From d7f4912b22cd2c36f85886727e84d4da2e8fc64f Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Mon, 20 Jul 2026 18:52:48 +0530 Subject: [PATCH 01/52] initial commit --- mobile-app-new/.gitignore | 45 ++ mobile-app-new/.metadata | 33 + mobile-app-new/README.md | 17 + mobile-app-new/analysis_options.yaml | 28 + mobile-app-new/android/.gitignore | 14 + mobile-app-new/android/app/build.gradle.kts | 45 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 ++ .../mobile_app_new/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + mobile-app-new/android/build.gradle.kts | 24 + mobile-app-new/android/gradle.properties | 6 + .../gradle/wrapper/gradle-wrapper.properties | 5 + mobile-app-new/android/settings.gradle.kts | 26 + mobile-app-new/ios/.gitignore | 34 + .../ios/Flutter/AppFrameworkInfo.plist | 24 + mobile-app-new/ios/Flutter/Debug.xcconfig | 1 + mobile-app-new/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 647 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 119 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + mobile-app-new/ios/Runner/AppDelegate.swift | 16 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + mobile-app-new/ios/Runner/Info.plist | 70 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + mobile-app-new/ios/Runner/SceneDelegate.swift | 6 + .../ios/RunnerTests/RunnerTests.swift | 12 + mobile-app-new/lib/main.dart | 122 ++++ mobile-app-new/pubspec.lock | 213 ++++++ mobile-app-new/pubspec.yaml | 89 +++ mobile-app-new/test/widget_test.dart | 30 + 67 files changed, 2011 insertions(+) create mode 100644 mobile-app-new/.gitignore create mode 100644 mobile-app-new/.metadata create mode 100644 mobile-app-new/README.md create mode 100644 mobile-app-new/analysis_options.yaml create mode 100644 mobile-app-new/android/.gitignore create mode 100644 mobile-app-new/android/app/build.gradle.kts create mode 100644 mobile-app-new/android/app/src/debug/AndroidManifest.xml create mode 100644 mobile-app-new/android/app/src/main/AndroidManifest.xml create mode 100644 mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt create mode 100644 mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 mobile-app-new/android/app/src/main/res/drawable/launch_background.xml create mode 100644 mobile-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 mobile-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 mobile-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 mobile-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 mobile-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 mobile-app-new/android/app/src/main/res/values-night/styles.xml create mode 100644 mobile-app-new/android/app/src/main/res/values/styles.xml create mode 100644 mobile-app-new/android/app/src/profile/AndroidManifest.xml create mode 100644 mobile-app-new/android/build.gradle.kts create mode 100644 mobile-app-new/android/gradle.properties create mode 100644 mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 mobile-app-new/android/settings.gradle.kts create mode 100644 mobile-app-new/ios/.gitignore create mode 100644 mobile-app-new/ios/Flutter/AppFrameworkInfo.plist create mode 100644 mobile-app-new/ios/Flutter/Debug.xcconfig create mode 100644 mobile-app-new/ios/Flutter/Release.xcconfig create mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.pbxproj create mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 mobile-app-new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 mobile-app-new/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 mobile-app-new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 mobile-app-new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 mobile-app-new/ios/Runner/AppDelegate.swift create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 mobile-app-new/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 mobile-app-new/ios/Runner/Base.lproj/Main.storyboard create mode 100644 mobile-app-new/ios/Runner/Info.plist create mode 100644 mobile-app-new/ios/Runner/Runner-Bridging-Header.h create mode 100644 mobile-app-new/ios/Runner/SceneDelegate.swift create mode 100644 mobile-app-new/ios/RunnerTests/RunnerTests.swift create mode 100644 mobile-app-new/lib/main.dart create mode 100644 mobile-app-new/pubspec.lock create mode 100644 mobile-app-new/pubspec.yaml create mode 100644 mobile-app-new/test/widget_test.dart diff --git a/mobile-app-new/.gitignore b/mobile-app-new/.gitignore new file mode 100644 index 000000000..3820a95c6 --- /dev/null +++ b/mobile-app-new/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +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-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/mobile-app-new/.metadata b/mobile-app-new/.metadata new file mode 100644 index 000000000..2059ed534 --- /dev/null +++ b/mobile-app-new/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "e1fd963c6f6922bd32afde2e9698a363cd0406d2" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 + base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 + - platform: android + create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 + base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 + - platform: ios + create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 + base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/mobile-app-new/README.md b/mobile-app-new/README.md new file mode 100644 index 000000000..6c4791dd9 --- /dev/null +++ b/mobile-app-new/README.md @@ -0,0 +1,17 @@ +# mobile_app_new + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml new file mode 100644 index 000000000..0d2902135 --- /dev/null +++ b/mobile-app-new/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/mobile-app-new/android/.gitignore b/mobile-app-new/android/.gitignore new file mode 100644 index 000000000..be3943c96 --- /dev/null +++ b/mobile-app-new/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/mobile-app-new/android/app/build.gradle.kts b/mobile-app-new/android/app/build.gradle.kts new file mode 100644 index 000000000..5a51d394c --- /dev/null +++ b/mobile-app-new/android/app/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "org.freecodecamp.mobile_app_new" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "org.freecodecamp.mobile_app_new" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/mobile-app-new/android/app/src/debug/AndroidManifest.xml b/mobile-app-new/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/mobile-app-new/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile-app-new/android/app/src/main/AndroidManifest.xml b/mobile-app-new/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..d94a739fe --- /dev/null +++ b/mobile-app-new/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt b/mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt new file mode 100644 index 000000000..dd81e9691 --- /dev/null +++ b/mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt @@ -0,0 +1,5 @@ +package org.freecodecamp.mobile_app_new + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile-app-new/android/app/src/main/res/drawable/launch_background.xml b/mobile-app-new/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/mobile-app-new/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/mobile-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/mobile-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/mobile-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/mobile-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/mobile-app-new/android/app/src/main/res/values-night/styles.xml b/mobile-app-new/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/mobile-app-new/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile-app-new/android/app/src/main/res/values/styles.xml b/mobile-app-new/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..cb1ef8805 --- /dev/null +++ b/mobile-app-new/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/mobile-app-new/android/app/src/profile/AndroidManifest.xml b/mobile-app-new/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..399f6981d --- /dev/null +++ b/mobile-app-new/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/mobile-app-new/android/build.gradle.kts b/mobile-app-new/android/build.gradle.kts new file mode 100644 index 000000000..dbee657bb --- /dev/null +++ b/mobile-app-new/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/mobile-app-new/android/gradle.properties b/mobile-app-new/android/gradle.properties new file mode 100644 index 000000000..e96108cfe --- /dev/null +++ b/mobile-app-new/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties b/mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..2d428bfb1 --- /dev/null +++ b/mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/mobile-app-new/android/settings.gradle.kts b/mobile-app-new/android/settings.gradle.kts new file mode 100644 index 000000000..c21f0c5b4 --- /dev/null +++ b/mobile-app-new/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/mobile-app-new/ios/.gitignore b/mobile-app-new/ios/.gitignore new file mode 100644 index 000000000..7a7f9873a --- /dev/null +++ b/mobile-app-new/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/mobile-app-new/ios/Flutter/AppFrameworkInfo.plist b/mobile-app-new/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..391a902b2 --- /dev/null +++ b/mobile-app-new/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/mobile-app-new/ios/Flutter/Debug.xcconfig b/mobile-app-new/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/mobile-app-new/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile-app-new/ios/Flutter/Release.xcconfig b/mobile-app-new/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/mobile-app-new/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/mobile-app-new/ios/Runner.xcodeproj/project.pbxproj b/mobile-app-new/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..c94a77c31 --- /dev/null +++ b/mobile-app-new/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,647 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, + ); + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = L33K9LWVP9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = L33K9LWVP9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = L33K9LWVP9; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile-app-new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile-app-new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..c3fedb29c --- /dev/null +++ b/mobile-app-new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile-app-new/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile-app-new/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/mobile-app-new/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/mobile-app-new/ios/Runner/AppDelegate.swift b/mobile-app-new/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..c30b367ec --- /dev/null +++ b/mobile-app-new/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/mobile-app-new/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile-app-new/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/mobile-app-new/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile-app-new/ios/Runner/Base.lproj/Main.storyboard b/mobile-app-new/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/mobile-app-new/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile-app-new/ios/Runner/Info.plist b/mobile-app-new/ios/Runner/Info.plist new file mode 100644 index 000000000..99d1642af --- /dev/null +++ b/mobile-app-new/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Mobile App New + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + mobile_app_new + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/mobile-app-new/ios/Runner/Runner-Bridging-Header.h b/mobile-app-new/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/mobile-app-new/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/mobile-app-new/ios/Runner/SceneDelegate.swift b/mobile-app-new/ios/Runner/SceneDelegate.swift new file mode 100644 index 000000000..b9ce8ea2b --- /dev/null +++ b/mobile-app-new/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/mobile-app-new/ios/RunnerTests/RunnerTests.swift b/mobile-app-new/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 000000000..86a7c3b1b --- /dev/null +++ b/mobile-app-new/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart new file mode 100644 index 000000000..244a702cf --- /dev/null +++ b/mobile-app-new/lib/main.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // TRY THIS: Try running your application with "flutter run". You'll see + // the application has a purple toolbar. Then, without quitting the app, + // try changing the seedColor in the colorScheme below to Colors.green + // and then invoke "hot reload" (save your changes or press the "hot + // reload" button in a Flutter-supported IDE, or press "r" if you used + // the command line to start the app). + // + // Notice that the counter didn't reset back to zero; the application + // state is not lost during the reload. To reset the state, use hot + // restart instead. + // + // This works for code too, not just values: Most code changes can be + // tested with just a hot reload. + colorScheme: .fromSeed(seedColor: Colors.deepPurple), + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // TRY THIS: Try changing the color here to a specific color (to + // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar + // change color while the other colors stay the same. + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + // + // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" + // action in the IDE, or press "p" in the console), to see the + // wireframe for each widget. + mainAxisAlignment: .center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock new file mode 100644 index 000000000..582d01a21 --- /dev/null +++ b/mobile-app-new/pubspec.lock @@ -0,0 +1,213 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" + source: hosted + version: "1.18.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.12.2 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml new file mode 100644 index 000000000..aa32e0aa2 --- /dev/null +++ b/mobile-app-new/pubspec.yaml @@ -0,0 +1,89 @@ +name: mobile_app_new +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.12.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/mobile-app-new/test/widget_test.dart b/mobile-app-new/test/widget_test.dart new file mode 100644 index 000000000..9209bf13c --- /dev/null +++ b/mobile-app-new/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:mobile_app_new/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} From e3f8ed43676efb3ec5e8c20f09b1ce413a3b0c3b Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Mon, 20 Jul 2026 20:51:51 +0530 Subject: [PATCH 02/52] chore: install dio, go_router and riverpod packages --- mobile-app-new/analysis_options.yaml | 3 + mobile-app-new/pubspec.lock | 503 ++++++++++++++++++++++++++- mobile-app-new/pubspec.yaml | 6 + 3 files changed, 511 insertions(+), 1 deletion(-) diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml index 0d2902135..0ae928e36 100644 --- a/mobile-app-new/analysis_options.yaml +++ b/mobile-app-new/analysis_options.yaml @@ -26,3 +26,6 @@ linter: # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options + +plugins: + riverpod_lint: 3.1.4 diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 582d01a21..eb3f076c4 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -1,6 +1,38 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: a49d6cf99e8d8e7a8e93668d09ced0bbdb954d0b4fccc2f5f9241c6b87fad95c + url: "https://pub.dev" + source: hosted + version: "99.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "663efa951fb8a45e06f491223a604c93820598f20e6a99c25617a1576065e8b7" + url: "https://pub.dev" + source: hosted + version: "12.1.0" + analyzer_buffer: + dependency: transitive + description: + name: analyzer_buffer + sha256: "445b77e2054fa3e8c8a8ef1b5e9e6b23bb8028fffd34b5e60eaef315b7750674" + url: "https://pub.dev" + source: hosted + version: "0.3.3" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -17,6 +49,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "45d14a0fb23e018d8287c32fc98d726ce466b231928ed9b9200f29bd3ccd39ae" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + build_config: + dependency: transitive + description: + name: build_config + sha256: f2c223156a26eea323e6244b85141d76413a80aeee9fe0b380773789fabaf8ae + url: "https://pub.dev" + source: hosted + version: "1.3.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 + url: "https://pub.dev" + source: hosted + version: "4.1.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "5367e521935b102bdf1e735d2aab461e36b2edca6517662d088dd04cc39f8d16" + url: "https://pub.dev" + source: hosted + version: "2.15.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" characters: dependency: transitive description: @@ -25,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -33,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" collection: dependency: transitive description: @@ -41,6 +145,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -49,6 +177,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.9" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: a4c1ccfee44c7e75ed80484071a5c142a385345e658fd8bd7c4b5c97e7198f98 + url: "https://pub.dev" + source: hosted + version: "3.1.8" + dio: + dependency: "direct main" + description: + name: dio + sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 + url: "https://pub.dev" + source: hosted + version: "5.10.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 + url: "https://pub.dev" + source: hosted + version: "2.2.0" fake_async: dependency: transitive description: @@ -57,6 +209,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -70,11 +238,96 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "9255e1e3ad6e38906a1b4f8287678f95f378744c5b46b1985588543f3f19046e" + url: "https://pub.dev" + source: hosted + version: "3.3.2" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a" + url: "https://pub.dev" + source: hosted + version: "17.3.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" leak_tracker: dependency: transitive description: @@ -107,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -131,6 +392,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mockito: + dependency: transitive + description: + name: mockito + sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422 + url: "https://pub.dev" + source: hosted + version: "5.6.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" path: dependency: transitive description: @@ -139,11 +432,123 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "17100416c51db7810c71a7bb2c34d1f881faa0074fd452afb0c4db6f8f126c76" + url: "https://pub.dev" + source: hosted + version: "3.3.2" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "3e275138862ccc22ed61444a1f9a840f753094c367f28f4123f50289cd204d68" + url: "https://pub.dev" + source: hosted + version: "1.0.0-dev.10" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: "674dbb26e2db3d9253166faf4758c796af14146b8fbcf5e7102bc8a04cd359b8" + url: "https://pub.dev" + source: hosted + version: "4.0.3" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: "54d790c3fee1ae281c448801bfdbfaa9fd961a9d3998494e0fcd9ee32184d7eb" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + 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: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + url: "https://pub.dev" + source: hosted + version: "4.2.3" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -160,6 +565,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: @@ -168,6 +581,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -184,6 +605,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20" + url: "https://pub.dev" + source: hosted + version: "1.31.0" test_api: dependency: transitive description: @@ -192,6 +621,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.11" + test_core: + dependency: transitive + description: + name: test_core + sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34" + url: "https://pub.dev" + source: hosted + version: "0.6.17" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" vector_math: dependency: transitive description: @@ -208,6 +661,54 @@ packages: url: "https://pub.dev" source: hosted version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.12.2 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.38.0" diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index aa32e0aa2..f4fd5191e 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -34,6 +34,10 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + flutter_riverpod: ^3.3.2 + riverpod_annotation: ^4.0.3 + dio: ^5.10.0 + go_router: ^17.3.0 dev_dependencies: flutter_test: @@ -45,6 +49,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^6.0.0 + riverpod_generator: ^4.0.4 + build_runner: ^2.15.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 4be074d65a131fe0bbc2759dfcc72450594dd296 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 21 Jul 2026 07:55:20 +0530 Subject: [PATCH 03/52] feat: basic setup --- mobile-app-new/lib/fcc_theme.dart | 127 ++++++++++++++++++ mobile-app-new/lib/main.dart | 30 ++--- mobile-app-new/lib/router/news.dart | 8 ++ mobile-app-new/lib/router/router.dart | 14 ++ mobile-app-new/lib/ui/core/drawer/drawer.dart | 30 +++++ .../lib/ui/core/drawer/drawer_tile.dart | 44 ++++++ .../views/news/news-feed/news_feed_view.dart | 20 +++ 7 files changed, 252 insertions(+), 21 deletions(-) create mode 100644 mobile-app-new/lib/fcc_theme.dart create mode 100644 mobile-app-new/lib/router/news.dart create mode 100644 mobile-app-new/lib/router/router.dart create mode 100644 mobile-app-new/lib/ui/core/drawer/drawer.dart create mode 100644 mobile-app-new/lib/ui/core/drawer/drawer_tile.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart diff --git a/mobile-app-new/lib/fcc_theme.dart b/mobile-app-new/lib/fcc_theme.dart new file mode 100644 index 000000000..3a9232d97 --- /dev/null +++ b/mobile-app-new/lib/fcc_theme.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; + +// Based on https://github.com/freeCodeCamp/ui/blob/01048178d606f404256c8f02299f6323c25e6a73/src/colors.css#L1-L50 +class FccColors { + static const gray00 = Color(0xFFFFFFFF); + static const gray05 = Color(0xFFF5F6F7); + static const gray10 = Color(0xFFDFDFE2); + static const gray15 = Color(0xFFD0D0D5); + static const gray45 = Color(0xFF858591); + static const gray75 = Color(0xFF3B3B4F); + static const gray80 = Color(0xFF2A2A40); + static const gray85 = Color(0xFF1B1B32); + static const gray90 = Color(0xFF0A0A23); + + static const purple10 = Color(0xFFDBB8FF); + static const purple50 = Color(0xFF9400D3); + static const purple90 = Color(0xFF5A01A7); + + static const yellow05 = Color(0xFFFCF8E3); + static const yellow10 = Color(0xFFFAEBCC); + static const yellow40 = Color(0xFFFFC300); + static const yellow45 = Color(0xFFFFBF00); + static const yellow50 = Color(0xFFF1BE32); + static const yellow70 = Color(0xFF8A6D3B); + static const yellow80 = Color(0xFF66512C); + static const yellow90 = Color(0xFF4D3800); + + static const blue05 = Color(0xFFD9EDF7); + static const blue10 = Color(0xFFBCE8F1); + static const blue30 = Color(0xFF99C9FF); + static const blue50 = Color(0xFF198EEE); + static const blue70 = Color(0xFF31708F); + static const blue90 = Color(0xFF002EAD); + static const blue30Translucent = Color.fromRGBO(153, 201, 255, 0.3); + static const blue90Translucent = Color.fromRGBO(0, 46, 173, 0.3); + + static const green05 = Color(0xFFDFF0D8); + static const green10 = Color(0xFFD6E9C6); + static const green40 = Color(0xFFACD157); + static const green70 = Color(0xFF3C763D); + static const green80 = Color(0xFF19562A); + static const green90 = Color(0xFF00471B); + + static const red05 = Color(0xFFF2DEDE); + static const red10 = Color(0xFFEBCCD1); + static const red15 = Color(0xFFFFADAD); + static const red30 = Color(0xFFF8577C); + static const red70 = Color(0xFFA94442); + static const red80 = Color(0xFFF82153); + static const red90 = Color(0xFF850000); + static const red100 = Color(0xFF5A3535); + + static const orange30 = Color(0xFFEDA971); +} + +// Based on https://github.com/freeCodeCamp/ui/blob/01048178d606f404256c8f02299f6323c25e6a73/src/colors.css#L120-L140 +class FccSemanticColors { + static const foregroundPrimary = FccColors.gray00; + static const foregroundSecondary = FccColors.gray05; + static const foregroundTertiary = FccColors.gray10; + static const foregroundQuaternary = FccColors.gray15; + static const foregroundDanger = FccColors.red90; + static const foregroundSuccess = FccColors.green90; + static const foregroundInfo = FccColors.blue90; + static const foregroundWarning = FccColors.yellow40; + static const foregroundCta = FccColors.gray90; + + static const backgroundPrimary = FccColors.gray90; + static const backgroundSecondary = FccColors.gray85; + static const backgroundTertiary = FccColors.gray80; + static const backgroundQuaternary = FccColors.gray75; + static const backgroundDanger = FccColors.red15; + static const backgroundSuccess = FccColors.green40; + static const backgroundInfo = FccColors.blue30; + static const backgroundSelection = FccColors.blue30Translucent; + static const backgroundCta = FccColors.yellow40; +} + +class FccTheme { + static ThemeData themeDark = ThemeData( + brightness: Brightness.dark, + fontFamily: 'Lato', + scaffoldBackgroundColor: const Color.fromRGBO(0x2A, 0x2A, 0x40, 1), + appBarTheme: const AppBarThemeData( + centerTitle: true, + backgroundColor: Color(0xFF0a0a23), + scrolledUnderElevation: 0, + ), + bottomNavigationBarTheme: const BottomNavigationBarThemeData( + backgroundColor: Color(0xFF0a0a23), + unselectedItemColor: Color(0x99FFFFFF), + selectedItemColor: Colors.white, + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF0a0a23), + foregroundColor: Colors.white, + ), + ), + textButtonTheme: TextButtonThemeData( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith( + (states) => const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1)), + foregroundColor: + WidgetStateProperty.resolveWith((states) => Colors.white), + overlayColor: WidgetStateProperty.resolveWith( + (states) => const Color(0x4DFFFFFF), + ), + ), + ), + canvasColor: Colors.white, + colorScheme: const ColorScheme.dark( + primary: Colors.white, + secondary: Color.fromRGBO(0xa9, 0xaa, 0xb2, 1), + surface: Color(0xFF0a0a23), + error: Colors.red, + ), + primaryColorDark: const Color(0xFF0a0a23), + primaryIconTheme: ThemeData.dark().primaryIconTheme.copyWith( + color: Colors.orange, + ), + textSelectionTheme: const TextSelectionThemeData( + cursorColor: Color.fromRGBO(66, 133, 244, 1.0), + selectionHandleColor: Color.fromARGB(255, 255, 255, 255), + ), + ); +} diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index 244a702cf..d716fd9c6 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/router/router.dart'; +import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; void main() { - runApp(const MyApp()); + runApp(ProviderScope(child: const MyApp())); } class MyApp extends StatelessWidget { @@ -10,27 +14,10 @@ class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( + return MaterialApp.router(routerConfig: router, title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + theme: FccTheme.themeDark, + // home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } @@ -85,6 +72,7 @@ class _MyHomePageState extends State { // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), + drawer: const DrawerWidget(), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. diff --git a/mobile-app-new/lib/router/news.dart b/mobile-app-new/lib/router/news.dart new file mode 100644 index 000000000..f97a258cb --- /dev/null +++ b/mobile-app-new/lib/router/news.dart @@ -0,0 +1,8 @@ +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; + +const newsFeedPath = '/news'; + +final newsRoutes = [ + GoRoute(path: newsFeedPath, builder: (context, state) => NewsFeedView()), +]; diff --git a/mobile-app-new/lib/router/router.dart b/mobile-app-new/lib/router/router.dart new file mode 100644 index 000000000..b3a879239 --- /dev/null +++ b/mobile-app-new/lib/router/router.dart @@ -0,0 +1,14 @@ +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/main.dart'; +import 'package:mobile_app_new/router/news.dart'; + +final router = GoRouter( + routes: [ + GoRoute( + path: '/', + builder: (context, state) => + const MyHomePage(title: 'Flutter Demo Home Page'), + ), + ...newsRoutes, + ], +); diff --git a/mobile-app-new/lib/ui/core/drawer/drawer.dart b/mobile-app-new/lib/ui/core/drawer/drawer.dart new file mode 100644 index 000000000..b303a8673 --- /dev/null +++ b/mobile-app-new/lib/ui/core/drawer/drawer.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/router/news.dart'; +import 'package:mobile_app_new/ui/core/drawer/drawer_tile.dart'; + +class DrawerWidget extends StatelessWidget { + const DrawerWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Drawer( + backgroundColor: FccColors.gray80, + child: ListView( + children: [ + DrawerTile( + component: 'Home', + icon: Icons.home, + route: () => context.go('/'), + ), + DrawerTile( + component: 'Tutorials', + icon: Icons.forum_outlined, + route: () => context.go(newsFeedPath), + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/core/drawer/drawer_tile.dart b/mobile-app-new/lib/ui/core/drawer/drawer_tile.dart new file mode 100644 index 000000000..409339497 --- /dev/null +++ b/mobile-app-new/lib/ui/core/drawer/drawer_tile.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +class DrawerTile extends StatelessWidget { + const DrawerTile({ + super.key, + this.textColor = Colors.white, + required this.component, + required this.route, + this.icon, + }); + + final String component; + final IconData? icon; + final Function route; + final Color? textColor; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(10.0), + child: ListTile( + dense: true, + leading: icon != null + ? Icon(icon, color: textColor) + : Image.asset( + 'assets/images/logo.png', + width: 30, + height: 30, + color: Colors.white, + ), + title: Text( + component, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: textColor, + letterSpacing: 0.5, + ), + ), + onTap: () => route(), + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart new file mode 100644 index 000000000..dea118254 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; + +class NewsFeedView extends StatefulWidget { + const NewsFeedView({super.key}); + + @override + State createState() => _NewsFeedViewState(); +} + +class _NewsFeedViewState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Tutorials')), + drawer: const DrawerWidget(), + body: Center(child: Text('LOADING...')), + ); + } +} From f15725619492c607bf36cbdd94e36a5d437c6b31 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 21 Jul 2026 21:32:48 +0530 Subject: [PATCH 04/52] feat: dio service --- mobile-app-new/lib/main.dart | 10 +++++++-- mobile-app-new/lib/services/dio_service.dart | 23 ++++++++++++++++++++ mobile-app-new/pubspec.lock | 16 ++++++++++++++ mobile-app-new/pubspec.yaml | 5 ++++- 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 mobile-app-new/lib/services/dio_service.dart diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index d716fd9c6..7f9bdca23 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -1,10 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/router/router.dart'; +import 'package:mobile_app_new/services/dio_service.dart'; import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; -void main() { +void main() async { + await dotenv.load(); + await DioService().init(); + runApp(ProviderScope(child: const MyApp())); } @@ -14,7 +19,8 @@ class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp.router(routerConfig: router, + return MaterialApp.router( + routerConfig: router, title: 'Flutter Demo', theme: FccTheme.themeDark, // home: const MyHomePage(title: 'Flutter Demo Home Page'), diff --git a/mobile-app-new/lib/services/dio_service.dart b/mobile-app-new/lib/services/dio_service.dart new file mode 100644 index 000000000..596469dc6 --- /dev/null +++ b/mobile-app-new/lib/services/dio_service.dart @@ -0,0 +1,23 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; + +class DioService { + static final DioService _dioService = DioService._internal(); + + static final Dio dio = Dio(); + + factory DioService() { + return _dioService; + } + + Future init() async { + bool isDevMode = dotenv.getBool('DEVELOPMENT_MODE', fallback: false); + + if (isDevMode) { + dio.interceptors.add(PrettyDioLogger(responseBody: false)); + } + } + + DioService._internal(); +} diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index eb3f076c4..55288ae9b 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -230,6 +230,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: d41da11fb497314fbf89811ec30af02d1d898b47980a129f0a8c0a1720460ba2 + url: "https://pub.dev" + source: hosted + version: "6.0.1" flutter_lints: dependency: "direct dev" description: @@ -440,6 +448,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + pretty_dio_logger: + dependency: "direct main" + description: + name: pretty_dio_logger + sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407" + url: "https://pub.dev" + source: hosted + version: "1.4.0" pub_semver: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index f4fd5191e..c6796c8da 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: riverpod_annotation: ^4.0.3 dio: ^5.10.0 go_router: ^17.3.0 + pretty_dio_logger: ^1.4.0 + flutter_dotenv: ^6.0.1 dev_dependencies: flutter_test: @@ -64,7 +66,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: + assets: + - .env # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg From 315d97d82cfb75bbac9ba104550c6ebf3f6cc140 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 24 Jul 2026 07:45:37 +0530 Subject: [PATCH 05/52] ctrl + s --- mobile-app-new/analysis_options.yaml | 5 + mobile-app-new/lib/main.dart | 2 +- .../news/bookmarked_tutorial_model.dart | 20 ++ .../lib/models/news/tutorial_model.dart | 150 +++++++++ .../models/news/tutorial_model.freezed.dart | 301 ++++++++++++++++++ .../lib/models/news/tutorial_model.g.dart | 31 ++ .../lib/repositories/news_api_repository.dart | 269 ++++++++++++++++ .../lib/{router => routing}/news.dart | 0 .../lib/{router => routing}/router.dart | 2 +- .../lib/services/news/api_service.dart | 269 ++++++++++++++++ mobile-app-new/lib/ui/core/drawer/drawer.dart | 2 +- mobile-app-new/pubspec.lock | 132 +++++++- mobile-app-new/pubspec.yaml | 5 + 13 files changed, 1183 insertions(+), 5 deletions(-) create mode 100644 mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart create mode 100644 mobile-app-new/lib/models/news/tutorial_model.dart create mode 100644 mobile-app-new/lib/models/news/tutorial_model.freezed.dart create mode 100644 mobile-app-new/lib/models/news/tutorial_model.g.dart create mode 100644 mobile-app-new/lib/repositories/news_api_repository.dart rename mobile-app-new/lib/{router => routing}/news.dart (100%) rename mobile-app-new/lib/{router => routing}/router.dart (84%) create mode 100644 mobile-app-new/lib/services/news/api_service.dart diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml index 0ae928e36..ade444ad8 100644 --- a/mobile-app-new/analysis_options.yaml +++ b/mobile-app-new/analysis_options.yaml @@ -27,5 +27,10 @@ linter: # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options +analyzer: + language: + strict-raw-types: true + strict-casts: true + plugins: riverpod_lint: 3.1.4 diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index 7f9bdca23..3a80798f8 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/router/router.dart'; +import 'package:mobile_app_new/routing/router.dart'; import 'package:mobile_app_new/services/dio_service.dart'; import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; diff --git a/mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart b/mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart new file mode 100644 index 000000000..b9702e43e --- /dev/null +++ b/mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart @@ -0,0 +1,20 @@ +class BookmarkedTutorial { + late String id; + late String tutorialTitle; + late String authorName; + late String tutorialText; + + BookmarkedTutorial.fromMap(Map map) { + id = map['articleId']; + tutorialTitle = map['articleTitle']; + authorName = map['authorName']; + tutorialText = map['articleText']; + } + + BookmarkedTutorial({ + required this.id, + required this.tutorialTitle, + required this.authorName, + required this.tutorialText, + }); +} diff --git a/mobile-app-new/lib/models/news/tutorial_model.dart b/mobile-app-new/lib/models/news/tutorial_model.dart new file mode 100644 index 000000000..e373d171c --- /dev/null +++ b/mobile-app-new/lib/models/news/tutorial_model.dart @@ -0,0 +1,150 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'tutorial_model.freezed.dart'; +part 'tutorial_model.g.dart'; + +// class Tutorial { +// final String id; +// final String slug; +// final String title; +// final String? featureImage; +// final String? profileImage; +// final String authorId; +// final String authorName; +// final String authorSlug; +// final String? createdAt; +// final String? url; +// final String? text; + +// Tutorial({ +// required this.id, +// required this.slug, +// required this.featureImage, +// required this.title, +// required this.profileImage, +// required this.authorId, +// required this.authorName, +// required this.authorSlug, +// this.createdAt, +// this.url, +// this.text, +// }); + +// // static List returnTags( +// // list, { +// // bool compact = false, +// // }) { +// // List tags = []; + +// // for (int i = 0; i < list.length; i++) { +// // tags.add(TagButton( +// // tagName: list[i]['name'], +// // tagSlug: list[i]['slug'] ?? list[i]['id'], +// // key: UniqueKey(), +// // compact: compact, +// // )); +// // } +// // return tags; +// // } + +// // this factory is for the endpoint where all tutorial thumbnails are received + +// // factory Tutorial.fromJson(dynamic data) { +// // return Tutorial( +// // createdAt: data['publishedAt'], +// // featureImage: data['coverImage']['url'], +// // title: data['title'], +// // profileImage: data['author']['profilePicture'], +// // authorId: data['author']['id'], +// // authorName: data['author']['name'], +// // authorSlug: data['author']['username'], +// // tagNames: returnTags(data['tags']), +// // rawTags: data['tags'] ?? [], +// // id: data['id'], +// // slug: data['slug'], +// // ); +// // } + +// // factory Tutorial.fromSearch(Map data) { +// // return Tutorial( +// // createdAt: data['publishedAt'], +// // featureImage: data['featureImage'], +// // title: data['title'], +// // profileImage: data['author']['profileImage'], +// // authorId: data['author']['id'], +// // authorName: data['author']['name'], +// // authorSlug: returnSlug(data['author']['url']), +// // tagNames: returnTags(data['tags']), +// // rawTags: data['tags'] ?? [], +// // id: data['objectID'], +// // slug: data['slug'], +// // ); +// // } + +// // static String returnSlug(String url) { +// // List splitUrl = url.split('/'); + +// // return splitUrl[splitUrl.length - 2]; +// // } + +// // this is factory is for the post view + +// // factory Tutorial.toPostFromJson(Map json) { +// // return Tutorial( +// // authorId: json['author']['id'], +// // authorName: json['author']['name'], +// // authorSlug: json['author']['username'], +// // profileImage: json['author']['profilePicture'], +// // tagNames: returnTags(json['tags']), +// // rawTags: json['tags'] ?? [], +// // id: json['id'], +// // title: json['title'], +// // url: json['url'], +// // text: json['content']['html'], +// // featureImage: json['coverImage']['url'], +// // slug: json['slug'], +// // ); +// // } +// } + +@freezed +abstract class Author with _$Author { + const factory Author({ + required String slug, + required String id, + required String? name, + required String? profileImage, + required String? bio, + String? website, + String? location, + String? facebook, + String? twitter, + }) = _Author; + + factory Author.fromJson(Map json) => _$AuthorFromJson(json); + + // final String slug; + // final String id; + // final String name; + // final String? profileImage; + // final String? bio; + // // TODO: Below items are not displayed in the UI. To be added + // final String? website; + // final String? location; + // final String? facebook; + // final String? twitter; + + // factory Author.toAuthorFromJson(Map data) { + // return Author( + // slug: data['username'], + // id: data['id'], + // name: data['name'], + // profileImage: data['profilePicture'], + // bio: data['bio']['text'], + // website: data['socialMediaLinks']['website'], + // location: data['location'], + // facebook: data['socialMediaLinks']['facebook'], + // twitter: data['socialMediaLinks']['twitter'], + // ); + // } +} diff --git a/mobile-app-new/lib/models/news/tutorial_model.freezed.dart b/mobile-app-new/lib/models/news/tutorial_model.freezed.dart new file mode 100644 index 000000000..a8a938af9 --- /dev/null +++ b/mobile-app-new/lib/models/news/tutorial_model.freezed.dart @@ -0,0 +1,301 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'tutorial_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Author { + + String get slug; String get id; String? get name; String? get profileImage; String? get bio; String? get website; String? get location; String? get facebook; String? get twitter; +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthorCopyWith get copyWith => _$AuthorCopyWithImpl(this as Author, _$identity); + + /// Serializes this Author to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Author&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.website, website) || other.website == website)&&(identical(other.location, location) || other.location == location)&&(identical(other.facebook, facebook) || other.facebook == facebook)&&(identical(other.twitter, twitter) || other.twitter == twitter)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,slug,id,name,profileImage,bio,website,location,facebook,twitter); + +@override +String toString() { + return 'Author(slug: $slug, id: $id, name: $name, profileImage: $profileImage, bio: $bio, website: $website, location: $location, facebook: $facebook, twitter: $twitter)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthorCopyWith<$Res> { + factory $AuthorCopyWith(Author value, $Res Function(Author) _then) = _$AuthorCopyWithImpl; +@useResult +$Res call({ + String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter +}); + + + + +} +/// @nodoc +class _$AuthorCopyWithImpl<$Res> + implements $AuthorCopyWith<$Res> { + _$AuthorCopyWithImpl(this._self, this._then); + + final Author _self; + final $Res Function(Author) _then; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? slug = null,Object? id = null,Object? name = freezed,Object? profileImage = freezed,Object? bio = freezed,Object? website = freezed,Object? location = freezed,Object? facebook = freezed,Object? twitter = freezed,}) { + return _then(_self.copyWith( +slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String?,profileImage: freezed == profileImage ? _self.profileImage : profileImage // ignore: cast_nullable_to_non_nullable +as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable +as String?,website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable +as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable +as String?,facebook: freezed == facebook ? _self.facebook : facebook // ignore: cast_nullable_to_non_nullable +as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Author]. +extension AuthorPatterns on Author { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Author value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Author value) $default,){ +final _that = this; +switch (_that) { +case _Author(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Author value)? $default,){ +final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that.slug,_that.id,_that.name,_that.profileImage,_that.bio,_that.website,_that.location,_that.facebook,_that.twitter);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter) $default,) {final _that = this; +switch (_that) { +case _Author(): +return $default(_that.slug,_that.id,_that.name,_that.profileImage,_that.bio,_that.website,_that.location,_that.facebook,_that.twitter);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter)? $default,) {final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that.slug,_that.id,_that.name,_that.profileImage,_that.bio,_that.website,_that.location,_that.facebook,_that.twitter);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Author implements Author { + const _Author({required this.slug, required this.id, required this.name, required this.profileImage, required this.bio, this.website, this.location, this.facebook, this.twitter}); + factory _Author.fromJson(Map json) => _$AuthorFromJson(json); + +@override final String slug; +@override final String id; +@override final String? name; +@override final String? profileImage; +@override final String? bio; +@override final String? website; +@override final String? location; +@override final String? facebook; +@override final String? twitter; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AuthorCopyWith<_Author> get copyWith => __$AuthorCopyWithImpl<_Author>(this, _$identity); + +@override +Map toJson() { + return _$AuthorToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Author&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.website, website) || other.website == website)&&(identical(other.location, location) || other.location == location)&&(identical(other.facebook, facebook) || other.facebook == facebook)&&(identical(other.twitter, twitter) || other.twitter == twitter)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,slug,id,name,profileImage,bio,website,location,facebook,twitter); + +@override +String toString() { + return 'Author(slug: $slug, id: $id, name: $name, profileImage: $profileImage, bio: $bio, website: $website, location: $location, facebook: $facebook, twitter: $twitter)'; +} + + +} + +/// @nodoc +abstract mixin class _$AuthorCopyWith<$Res> implements $AuthorCopyWith<$Res> { + factory _$AuthorCopyWith(_Author value, $Res Function(_Author) _then) = __$AuthorCopyWithImpl; +@override @useResult +$Res call({ + String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter +}); + + + + +} +/// @nodoc +class __$AuthorCopyWithImpl<$Res> + implements _$AuthorCopyWith<$Res> { + __$AuthorCopyWithImpl(this._self, this._then); + + final _Author _self; + final $Res Function(_Author) _then; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? slug = null,Object? id = null,Object? name = freezed,Object? profileImage = freezed,Object? bio = freezed,Object? website = freezed,Object? location = freezed,Object? facebook = freezed,Object? twitter = freezed,}) { + return _then(_Author( +slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String?,profileImage: freezed == profileImage ? _self.profileImage : profileImage // ignore: cast_nullable_to_non_nullable +as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable +as String?,website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable +as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable +as String?,facebook: freezed == facebook ? _self.facebook : facebook // ignore: cast_nullable_to_non_nullable +as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/models/news/tutorial_model.g.dart b/mobile-app-new/lib/models/news/tutorial_model.g.dart new file mode 100644 index 000000000..3fc7544b9 --- /dev/null +++ b/mobile-app-new/lib/models/news/tutorial_model.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tutorial_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Author _$AuthorFromJson(Map json) => _Author( + slug: json['slug'] as String, + id: json['id'] as String, + name: json['name'] as String?, + profileImage: json['profileImage'] as String?, + bio: json['bio'] as String?, + website: json['website'] as String?, + location: json['location'] as String?, + facebook: json['facebook'] as String?, + twitter: json['twitter'] as String?, +); + +Map _$AuthorToJson(_Author instance) => { + 'slug': instance.slug, + 'id': instance.id, + 'name': instance.name, + 'profileImage': instance.profileImage, + 'bio': instance.bio, + 'website': instance.website, + 'location': instance.location, + 'facebook': instance.facebook, + 'twitter': instance.twitter, +}; diff --git a/mobile-app-new/lib/repositories/news_api_repository.dart b/mobile-app-new/lib/repositories/news_api_repository.dart new file mode 100644 index 000000000..7fe436711 --- /dev/null +++ b/mobile-app-new/lib/repositories/news_api_repository.dart @@ -0,0 +1,269 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:graphql/client.dart'; + +typedef ApiData = ({String endCursor, bool hasNextPage, List posts}); +typedef GetAllPostsT = Future; + +const apiUrl = 'https://gql-beta.hashnode.com'; + +const postsPerPage = 20; + +const postFieldsFragment = r''' + fragment PostFields on Post { + id + slug + title + url + author { + id + username + name + bio { + text + } + profilePicture + socialMediaLinks { + website + twitter + facebook + } + location + } + tags { + id + name + slug + } + coverImage { + url + } + brief + readTimeInMinutes + content { + html + } + seo { + description + } + publishedAt + updatedAt + } + '''; + +const getAllPostsQuery = + postFieldsFragment + + r''' + query GetAllPosts($publicationId: ObjectId!, $first: Int!, $after: String) { + publication(id: $publicationId) { + id + posts(first: $first, after: $after) { + edges { + node { + ...PostFields + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + } + '''; + +const getAuthorQuery = r''' + query GetAuthor($authorSlug: String!) { + user(username: $authorSlug) { + id + username + name + bio { + text + } + profilePicture + socialMediaLinks { + website + twitter + facebook + } + location + } + } + '''; + +const getPostsByAuthorQuery = + postFieldsFragment + + r''' + query GetPostsByAuthorQuery($first: Int!, $after: String, $filter: SearchPostsOfPublicationFilter!) { + searchPostsOfPublication(first: $first, after: $after, filter: $filter) { + edges { + node { + ...PostFields + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + '''; + +const getPostsByTagQuery = + postFieldsFragment + + r''' + query GetPostsByTagQuery($publicationId: ObjectId!, $first: Int!, $after: String, $filter: PublicationPostConnectionFilter!) { + publication(id: $publicationId) { + id + posts(first: $first, after: $after, filter: $filter) { + edges { + node { + ...PostFields + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + } + '''; + +const getPostQuery = + postFieldsFragment + + r''' + query GetPost($id: ID!) { + post(id: $id) { + ...PostFields + } + } + '''; + +class NewsApiService { + static final NewsApiService _instance = NewsApiService._internal(); + factory NewsApiService() => _instance; + NewsApiService._internal(); + + final GraphQLClient client = GraphQLClient( + link: HttpLink(apiUrl), + cache: GraphQLCache(), + ); + final String publicationId = dotenv.get('HASHNODE_PUBLICATION_ID'); + + GetAllPostsT getAllPosts({String afterCursor = ''}) async { + final result = await client.query( + QueryOptions( + document: gql(getAllPostsQuery), + variables: { + 'publicationId': publicationId, + 'first': postsPerPage, + 'after': afterCursor, + }, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final List posts = result.data!['publication']['posts']['edges']; + final String endCursor = + result.data!['publication']['posts']['pageInfo']['endCursor']; + final bool hasNextPage = + result.data!['publication']['posts']['pageInfo']['hasNextPage']; + + return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + } + + Future> getPost(String postId) async { + final result = await client.query( + QueryOptions(document: gql(getPostQuery), variables: {'id': postId}), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final Map post = result.data!['post']; + + return post; + } + + Future> getAuthor(String authorSlug) async { + final result = await client.query( + QueryOptions( + document: gql(getAuthorQuery), + variables: {'authorSlug': authorSlug}, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final Map author = result.data!['user']; + + return author; + } + + GetAllPostsT getPostsByAuthor( + String authorId, { + String afterCursor = '', + }) async { + final result = await client.query( + QueryOptions( + document: gql(getPostsByAuthorQuery), + variables: { + 'first': postsPerPage, + 'after': afterCursor, + 'filter': { + 'publicationId': publicationId, + 'authorIds': [authorId], + }, + }, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final List posts = + result.data!['searchPostsOfPublication']['edges']; + final String endCursor = + result.data!['searchPostsOfPublication']['pageInfo']['endCursor']; + final bool hasNextPage = + result.data!['searchPostsOfPublication']['pageInfo']['hasNextPage']; + + return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + } + + GetAllPostsT getPostsByTag(String tagSlug, {String afterCursor = ''}) async { + final result = await client.query( + QueryOptions( + document: gql(getPostsByTagQuery), + variables: { + 'publicationId': publicationId, + 'first': postsPerPage, + 'after': afterCursor, + 'filter': { + 'tagSlugs': [tagSlug], + }, + }, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final List posts = result.data!['publication']['posts']['edges']; + final String endCursor = + result.data!['publication']['posts']['pageInfo']['endCursor']; + final bool hasNextPage = + result.data!['publication']['posts']['pageInfo']['hasNextPage']; + + return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + } +} diff --git a/mobile-app-new/lib/router/news.dart b/mobile-app-new/lib/routing/news.dart similarity index 100% rename from mobile-app-new/lib/router/news.dart rename to mobile-app-new/lib/routing/news.dart diff --git a/mobile-app-new/lib/router/router.dart b/mobile-app-new/lib/routing/router.dart similarity index 84% rename from mobile-app-new/lib/router/router.dart rename to mobile-app-new/lib/routing/router.dart index b3a879239..8e6f9a45c 100644 --- a/mobile-app-new/lib/router/router.dart +++ b/mobile-app-new/lib/routing/router.dart @@ -1,6 +1,6 @@ import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/main.dart'; -import 'package:mobile_app_new/router/news.dart'; +import 'package:mobile_app_new/routing/news.dart'; final router = GoRouter( routes: [ diff --git a/mobile-app-new/lib/services/news/api_service.dart b/mobile-app-new/lib/services/news/api_service.dart new file mode 100644 index 000000000..7fe436711 --- /dev/null +++ b/mobile-app-new/lib/services/news/api_service.dart @@ -0,0 +1,269 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:graphql/client.dart'; + +typedef ApiData = ({String endCursor, bool hasNextPage, List posts}); +typedef GetAllPostsT = Future; + +const apiUrl = 'https://gql-beta.hashnode.com'; + +const postsPerPage = 20; + +const postFieldsFragment = r''' + fragment PostFields on Post { + id + slug + title + url + author { + id + username + name + bio { + text + } + profilePicture + socialMediaLinks { + website + twitter + facebook + } + location + } + tags { + id + name + slug + } + coverImage { + url + } + brief + readTimeInMinutes + content { + html + } + seo { + description + } + publishedAt + updatedAt + } + '''; + +const getAllPostsQuery = + postFieldsFragment + + r''' + query GetAllPosts($publicationId: ObjectId!, $first: Int!, $after: String) { + publication(id: $publicationId) { + id + posts(first: $first, after: $after) { + edges { + node { + ...PostFields + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + } + '''; + +const getAuthorQuery = r''' + query GetAuthor($authorSlug: String!) { + user(username: $authorSlug) { + id + username + name + bio { + text + } + profilePicture + socialMediaLinks { + website + twitter + facebook + } + location + } + } + '''; + +const getPostsByAuthorQuery = + postFieldsFragment + + r''' + query GetPostsByAuthorQuery($first: Int!, $after: String, $filter: SearchPostsOfPublicationFilter!) { + searchPostsOfPublication(first: $first, after: $after, filter: $filter) { + edges { + node { + ...PostFields + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + '''; + +const getPostsByTagQuery = + postFieldsFragment + + r''' + query GetPostsByTagQuery($publicationId: ObjectId!, $first: Int!, $after: String, $filter: PublicationPostConnectionFilter!) { + publication(id: $publicationId) { + id + posts(first: $first, after: $after, filter: $filter) { + edges { + node { + ...PostFields + } + } + pageInfo { + endCursor + hasNextPage + } + } + } + } + '''; + +const getPostQuery = + postFieldsFragment + + r''' + query GetPost($id: ID!) { + post(id: $id) { + ...PostFields + } + } + '''; + +class NewsApiService { + static final NewsApiService _instance = NewsApiService._internal(); + factory NewsApiService() => _instance; + NewsApiService._internal(); + + final GraphQLClient client = GraphQLClient( + link: HttpLink(apiUrl), + cache: GraphQLCache(), + ); + final String publicationId = dotenv.get('HASHNODE_PUBLICATION_ID'); + + GetAllPostsT getAllPosts({String afterCursor = ''}) async { + final result = await client.query( + QueryOptions( + document: gql(getAllPostsQuery), + variables: { + 'publicationId': publicationId, + 'first': postsPerPage, + 'after': afterCursor, + }, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final List posts = result.data!['publication']['posts']['edges']; + final String endCursor = + result.data!['publication']['posts']['pageInfo']['endCursor']; + final bool hasNextPage = + result.data!['publication']['posts']['pageInfo']['hasNextPage']; + + return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + } + + Future> getPost(String postId) async { + final result = await client.query( + QueryOptions(document: gql(getPostQuery), variables: {'id': postId}), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final Map post = result.data!['post']; + + return post; + } + + Future> getAuthor(String authorSlug) async { + final result = await client.query( + QueryOptions( + document: gql(getAuthorQuery), + variables: {'authorSlug': authorSlug}, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final Map author = result.data!['user']; + + return author; + } + + GetAllPostsT getPostsByAuthor( + String authorId, { + String afterCursor = '', + }) async { + final result = await client.query( + QueryOptions( + document: gql(getPostsByAuthorQuery), + variables: { + 'first': postsPerPage, + 'after': afterCursor, + 'filter': { + 'publicationId': publicationId, + 'authorIds': [authorId], + }, + }, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final List posts = + result.data!['searchPostsOfPublication']['edges']; + final String endCursor = + result.data!['searchPostsOfPublication']['pageInfo']['endCursor']; + final bool hasNextPage = + result.data!['searchPostsOfPublication']['pageInfo']['hasNextPage']; + + return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + } + + GetAllPostsT getPostsByTag(String tagSlug, {String afterCursor = ''}) async { + final result = await client.query( + QueryOptions( + document: gql(getPostsByTagQuery), + variables: { + 'publicationId': publicationId, + 'first': postsPerPage, + 'after': afterCursor, + 'filter': { + 'tagSlugs': [tagSlug], + }, + }, + ), + ); + + if (result.hasException) { + throw Exception(result.exception.toString()); + } + + final List posts = result.data!['publication']['posts']['edges']; + final String endCursor = + result.data!['publication']['posts']['pageInfo']['endCursor']; + final bool hasNextPage = + result.data!['publication']['posts']['pageInfo']['hasNextPage']; + + return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + } +} diff --git a/mobile-app-new/lib/ui/core/drawer/drawer.dart b/mobile-app-new/lib/ui/core/drawer/drawer.dart index b303a8673..56b97fa34 100644 --- a/mobile-app-new/lib/ui/core/drawer/drawer.dart +++ b/mobile-app-new/lib/ui/core/drawer/drawer.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/router/news.dart'; +import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/ui/core/drawer/drawer_tile.dart'; class DrawerWidget extends StatelessWidget { diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 55288ae9b..a72ada970 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -264,8 +264,16 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "8599ba37236328ff6f97269ecce875d251a367eb2929c9bbf9b811b2ce56c74e" + url: "https://pub.dev" + source: hosted + version: "3.2.6-dev.1" freezed_annotation: - dependency: transitive + dependency: "direct main" description: name: freezed_annotation sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" @@ -296,6 +304,70 @@ packages: url: "https://pub.dev" source: hosted version: "17.3.0" + gql: + dependency: transitive + description: + name: gql + sha256: "67c32325eb55c15f526f0f5e7d8b38a463dbff2ec3c2e046be4a1a95f0dc93d1" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + gql_dedupe_link: + dependency: transitive + description: + name: gql_dedupe_link + sha256: "10bee0564d67c24e0c8bd08bd56e0682b64a135e58afabbeed30d85d5e9fea96" + url: "https://pub.dev" + source: hosted + version: "2.0.4-alpha+1715521079596" + gql_error_link: + dependency: transitive + description: + name: gql_error_link + sha256: dd0f3fbfbcec848ea050507470cdb5d3dc47d29544ae11044a1c883cbe159ccc + url: "https://pub.dev" + source: hosted + version: "1.0.1" + gql_exec: + dependency: transitive + description: + name: gql_exec + sha256: "394944626fae900f1d34343ecf2d62e44eb984826189c8979d305f0ae5846e38" + url: "https://pub.dev" + source: hosted + version: "1.1.1-alpha+1699813812660" + gql_http_link: + dependency: transitive + description: + name: gql_http_link + sha256: "07635e85a4f313836904961904417fd27844fe8f68f77b410a4e6b81d8e9202e" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + gql_link: + dependency: transitive + description: + name: gql_link + sha256: "0730276ce3a6a0ced073194ff923a8d99b3c78e442cbf096eb54fd0c3fa9f974" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + gql_transform_link: + dependency: transitive + description: + name: gql_transform_link + sha256: b3bb06a6991bc5c9d877e2757455f80e2c14dc684b8327bedae4f4ee67afae8b + url: "https://pub.dev" + source: hosted + version: "1.0.1" + graphql: + dependency: "direct main" + description: + name: graphql + sha256: a7cb0b5e8719546bf8d4edf5f57c3690ddf0fcce379c0d9d2287fdab73481090 + url: "https://pub.dev" + source: hosted + version: "5.2.4" graphs: dependency: transitive description: @@ -304,6 +376,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + hive_ce: + dependency: transitive + description: + name: hive_ce + sha256: "8e9980e68643afb1e765d3af32b47996552a64e190d03faf622cea07c1294418" + url: "https://pub.dev" + source: hosted + version: "2.19.3" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -328,14 +416,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - json_annotation: + isolate_channel: dependency: transitive + description: + name: isolate_channel + sha256: a9d3d620695bc984244dafae00b95e4319d6974b2d77f4b9e1eb4f2efe099094 + url: "https://pub.dev" + source: hosted + version: "0.6.1" + json_annotation: + dependency: "direct main" description: name: json_annotation sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" url: "https://pub.dev" source: hosted version: "4.12.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: ffcd10cde35a93b2abbbcc26bd9971f4ca93763e8abe78d855e3c4177797e501 + url: "https://pub.dev" + source: hosted + version: "6.14.0" leak_tracker: dependency: transitive description: @@ -424,6 +528,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + normalize: + dependency: transitive + description: + name: normalize + sha256: "703f0af9e6f43a5a71536e977b945238bc89f1a941347e7ba467865a20cc1a9f" + url: "https://pub.dev" + source: hosted + version: "0.10.0" package_config: dependency: transitive description: @@ -504,6 +616,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" shelf: dependency: transitive description: @@ -549,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.3" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" + url: "https://pub.dev" + source: hosted + version: "1.3.12" source_map_stack_trace: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index c6796c8da..daa4b0c7d 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -40,6 +40,9 @@ dependencies: go_router: ^17.3.0 pretty_dio_logger: ^1.4.0 flutter_dotenv: ^6.0.1 + graphql: ^5.2.4 + freezed_annotation: ^3.1.0 + json_annotation: ^4.12.0 dev_dependencies: flutter_test: @@ -53,6 +56,8 @@ dev_dependencies: flutter_lints: ^6.0.0 riverpod_generator: ^4.0.4 build_runner: ^2.15.1 + freezed: ^3.2.6-dev.1 + json_serializable: ^6.14.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 0116ddfce243d725526ee2b184a4220a2041cca1 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 28 Jul 2026 05:58:29 +0530 Subject: [PATCH 06/52] feat: author model --- .../lib/models/news/author_model.dart | 39 + .../lib/models/news/author_model.freezed.dart | 875 ++++++++++++++++++ .../lib/models/news/author_model.g.dart | 54 ++ 3 files changed, 968 insertions(+) create mode 100644 mobile-app-new/lib/models/news/author_model.dart create mode 100644 mobile-app-new/lib/models/news/author_model.freezed.dart create mode 100644 mobile-app-new/lib/models/news/author_model.g.dart diff --git a/mobile-app-new/lib/models/news/author_model.dart b/mobile-app-new/lib/models/news/author_model.dart new file mode 100644 index 000000000..fb3904d84 --- /dev/null +++ b/mobile-app-new/lib/models/news/author_model.dart @@ -0,0 +1,39 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'author_model.freezed.dart'; +part 'author_model.g.dart'; + +@freezed +abstract class Bio with _$Bio { + const factory Bio({String? text}) = _Bio; + + factory Bio.fromJson(Map json) => _$BioFromJson(json); +} + +@freezed +abstract class SocialMediaLinks with _$SocialMediaLinks { + const factory SocialMediaLinks({ + String? website, + String? twitter, + String? facebook, + }) = _SocialMediaLinks; + + factory SocialMediaLinks.fromJson(Map json) => + _$SocialMediaLinksFromJson(json); +} + +@freezed +abstract class Author with _$Author { + const factory Author({ + required String username, + required String id, + required String name, + String? profilePicture, + Bio? bio, + // TODO: Below items are not displayed in the UI. To be added + String? location, + SocialMediaLinks? socialMediaLinks, + }) = _Author; + + factory Author.fromJson(Map json) => _$AuthorFromJson(json); +} diff --git a/mobile-app-new/lib/models/news/author_model.freezed.dart b/mobile-app-new/lib/models/news/author_model.freezed.dart new file mode 100644 index 000000000..c5886abfb --- /dev/null +++ b/mobile-app-new/lib/models/news/author_model.freezed.dart @@ -0,0 +1,875 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'author_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Bio { + + String? get text; +/// Create a copy of Bio +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BioCopyWith get copyWith => _$BioCopyWithImpl(this as Bio, _$identity); + + /// Serializes this Bio to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Bio&&(identical(other.text, text) || other.text == text)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text); + +@override +String toString() { + return 'Bio(text: $text)'; +} + + +} + +/// @nodoc +abstract mixin class $BioCopyWith<$Res> { + factory $BioCopyWith(Bio value, $Res Function(Bio) _then) = _$BioCopyWithImpl; +@useResult +$Res call({ + String? text +}); + + + + +} +/// @nodoc +class _$BioCopyWithImpl<$Res> + implements $BioCopyWith<$Res> { + _$BioCopyWithImpl(this._self, this._then); + + final Bio _self; + final $Res Function(Bio) _then; + +/// Create a copy of Bio +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? text = freezed,}) { + return _then(_self.copyWith( +text: freezed == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Bio]. +extension BioPatterns on Bio { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Bio value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Bio() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Bio value) $default,){ +final _that = this; +switch (_that) { +case _Bio(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Bio value)? $default,){ +final _that = this; +switch (_that) { +case _Bio() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? text)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Bio() when $default != null: +return $default(_that.text);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? text) $default,) {final _that = this; +switch (_that) { +case _Bio(): +return $default(_that.text);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? text)? $default,) {final _that = this; +switch (_that) { +case _Bio() when $default != null: +return $default(_that.text);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Bio implements Bio { + const _Bio({this.text}); + factory _Bio.fromJson(Map json) => _$BioFromJson(json); + +@override final String? text; + +/// Create a copy of Bio +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BioCopyWith<_Bio> get copyWith => __$BioCopyWithImpl<_Bio>(this, _$identity); + +@override +Map toJson() { + return _$BioToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Bio&&(identical(other.text, text) || other.text == text)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text); + +@override +String toString() { + return 'Bio(text: $text)'; +} + + +} + +/// @nodoc +abstract mixin class _$BioCopyWith<$Res> implements $BioCopyWith<$Res> { + factory _$BioCopyWith(_Bio value, $Res Function(_Bio) _then) = __$BioCopyWithImpl; +@override @useResult +$Res call({ + String? text +}); + + + + +} +/// @nodoc +class __$BioCopyWithImpl<$Res> + implements _$BioCopyWith<$Res> { + __$BioCopyWithImpl(this._self, this._then); + + final _Bio _self; + final $Res Function(_Bio) _then; + +/// Create a copy of Bio +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? text = freezed,}) { + return _then(_Bio( +text: freezed == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$SocialMediaLinks { + + String? get website; String? get twitter; String? get facebook; +/// Create a copy of SocialMediaLinks +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SocialMediaLinksCopyWith get copyWith => _$SocialMediaLinksCopyWithImpl(this as SocialMediaLinks, _$identity); + + /// Serializes this SocialMediaLinks to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SocialMediaLinks&&(identical(other.website, website) || other.website == website)&&(identical(other.twitter, twitter) || other.twitter == twitter)&&(identical(other.facebook, facebook) || other.facebook == facebook)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,website,twitter,facebook); + +@override +String toString() { + return 'SocialMediaLinks(website: $website, twitter: $twitter, facebook: $facebook)'; +} + + +} + +/// @nodoc +abstract mixin class $SocialMediaLinksCopyWith<$Res> { + factory $SocialMediaLinksCopyWith(SocialMediaLinks value, $Res Function(SocialMediaLinks) _then) = _$SocialMediaLinksCopyWithImpl; +@useResult +$Res call({ + String? website, String? twitter, String? facebook +}); + + + + +} +/// @nodoc +class _$SocialMediaLinksCopyWithImpl<$Res> + implements $SocialMediaLinksCopyWith<$Res> { + _$SocialMediaLinksCopyWithImpl(this._self, this._then); + + final SocialMediaLinks _self; + final $Res Function(SocialMediaLinks) _then; + +/// Create a copy of SocialMediaLinks +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? website = freezed,Object? twitter = freezed,Object? facebook = freezed,}) { + return _then(_self.copyWith( +website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable +as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable +as String?,facebook: freezed == facebook ? _self.facebook : facebook // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SocialMediaLinks]. +extension SocialMediaLinksPatterns on SocialMediaLinks { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SocialMediaLinks value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SocialMediaLinks() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SocialMediaLinks value) $default,){ +final _that = this; +switch (_that) { +case _SocialMediaLinks(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SocialMediaLinks value)? $default,){ +final _that = this; +switch (_that) { +case _SocialMediaLinks() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? website, String? twitter, String? facebook)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SocialMediaLinks() when $default != null: +return $default(_that.website,_that.twitter,_that.facebook);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? website, String? twitter, String? facebook) $default,) {final _that = this; +switch (_that) { +case _SocialMediaLinks(): +return $default(_that.website,_that.twitter,_that.facebook);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? website, String? twitter, String? facebook)? $default,) {final _that = this; +switch (_that) { +case _SocialMediaLinks() when $default != null: +return $default(_that.website,_that.twitter,_that.facebook);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SocialMediaLinks implements SocialMediaLinks { + const _SocialMediaLinks({this.website, this.twitter, this.facebook}); + factory _SocialMediaLinks.fromJson(Map json) => _$SocialMediaLinksFromJson(json); + +@override final String? website; +@override final String? twitter; +@override final String? facebook; + +/// Create a copy of SocialMediaLinks +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SocialMediaLinksCopyWith<_SocialMediaLinks> get copyWith => __$SocialMediaLinksCopyWithImpl<_SocialMediaLinks>(this, _$identity); + +@override +Map toJson() { + return _$SocialMediaLinksToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SocialMediaLinks&&(identical(other.website, website) || other.website == website)&&(identical(other.twitter, twitter) || other.twitter == twitter)&&(identical(other.facebook, facebook) || other.facebook == facebook)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,website,twitter,facebook); + +@override +String toString() { + return 'SocialMediaLinks(website: $website, twitter: $twitter, facebook: $facebook)'; +} + + +} + +/// @nodoc +abstract mixin class _$SocialMediaLinksCopyWith<$Res> implements $SocialMediaLinksCopyWith<$Res> { + factory _$SocialMediaLinksCopyWith(_SocialMediaLinks value, $Res Function(_SocialMediaLinks) _then) = __$SocialMediaLinksCopyWithImpl; +@override @useResult +$Res call({ + String? website, String? twitter, String? facebook +}); + + + + +} +/// @nodoc +class __$SocialMediaLinksCopyWithImpl<$Res> + implements _$SocialMediaLinksCopyWith<$Res> { + __$SocialMediaLinksCopyWithImpl(this._self, this._then); + + final _SocialMediaLinks _self; + final $Res Function(_SocialMediaLinks) _then; + +/// Create a copy of SocialMediaLinks +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? website = freezed,Object? twitter = freezed,Object? facebook = freezed,}) { + return _then(_SocialMediaLinks( +website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable +as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable +as String?,facebook: freezed == facebook ? _self.facebook : facebook // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$Author { + + String get username; String get id; String get name; String? get profilePicture; Bio? get bio; String? get location; SocialMediaLinks? get socialMediaLinks; +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthorCopyWith get copyWith => _$AuthorCopyWithImpl(this as Author, _$identity); + + /// Serializes this Author to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Author&&(identical(other.username, username) || other.username == username)&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.profilePicture, profilePicture) || other.profilePicture == profilePicture)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.location, location) || other.location == location)&&(identical(other.socialMediaLinks, socialMediaLinks) || other.socialMediaLinks == socialMediaLinks)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,username,id,name,profilePicture,bio,location,socialMediaLinks); + +@override +String toString() { + return 'Author(username: $username, id: $id, name: $name, profilePicture: $profilePicture, bio: $bio, location: $location, socialMediaLinks: $socialMediaLinks)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthorCopyWith<$Res> { + factory $AuthorCopyWith(Author value, $Res Function(Author) _then) = _$AuthorCopyWithImpl; +@useResult +$Res call({ + String username, String id, String name, String? profilePicture, Bio? bio, String? location, SocialMediaLinks? socialMediaLinks +}); + + +$BioCopyWith<$Res>? get bio;$SocialMediaLinksCopyWith<$Res>? get socialMediaLinks; + +} +/// @nodoc +class _$AuthorCopyWithImpl<$Res> + implements $AuthorCopyWith<$Res> { + _$AuthorCopyWithImpl(this._self, this._then); + + final Author _self; + final $Res Function(Author) _then; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? username = null,Object? id = null,Object? name = null,Object? profilePicture = freezed,Object? bio = freezed,Object? location = freezed,Object? socialMediaLinks = freezed,}) { + return _then(_self.copyWith( +username: null == username ? _self.username : username // ignore: cast_nullable_to_non_nullable +as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,profilePicture: freezed == profilePicture ? _self.profilePicture : profilePicture // ignore: cast_nullable_to_non_nullable +as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable +as Bio?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable +as String?,socialMediaLinks: freezed == socialMediaLinks ? _self.socialMediaLinks : socialMediaLinks // ignore: cast_nullable_to_non_nullable +as SocialMediaLinks?, + )); +} +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$BioCopyWith<$Res>? get bio { + if (_self.bio == null) { + return null; + } + + return $BioCopyWith<$Res>(_self.bio!, (value) { + return _then(_self.copyWith(bio: value)); + }); +}/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SocialMediaLinksCopyWith<$Res>? get socialMediaLinks { + if (_self.socialMediaLinks == null) { + return null; + } + + return $SocialMediaLinksCopyWith<$Res>(_self.socialMediaLinks!, (value) { + return _then(_self.copyWith(socialMediaLinks: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [Author]. +extension AuthorPatterns on Author { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Author value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Author value) $default,){ +final _that = this; +switch (_that) { +case _Author(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Author value)? $default,){ +final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String username, String id, String name, String? profilePicture, Bio? bio, String? location, SocialMediaLinks? socialMediaLinks)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that.username,_that.id,_that.name,_that.profilePicture,_that.bio,_that.location,_that.socialMediaLinks);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String username, String id, String name, String? profilePicture, Bio? bio, String? location, SocialMediaLinks? socialMediaLinks) $default,) {final _that = this; +switch (_that) { +case _Author(): +return $default(_that.username,_that.id,_that.name,_that.profilePicture,_that.bio,_that.location,_that.socialMediaLinks);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String username, String id, String name, String? profilePicture, Bio? bio, String? location, SocialMediaLinks? socialMediaLinks)? $default,) {final _that = this; +switch (_that) { +case _Author() when $default != null: +return $default(_that.username,_that.id,_that.name,_that.profilePicture,_that.bio,_that.location,_that.socialMediaLinks);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Author implements Author { + const _Author({required this.username, required this.id, required this.name, this.profilePicture, this.bio, this.location, this.socialMediaLinks}); + factory _Author.fromJson(Map json) => _$AuthorFromJson(json); + +@override final String username; +@override final String id; +@override final String name; +@override final String? profilePicture; +@override final Bio? bio; +@override final String? location; +@override final SocialMediaLinks? socialMediaLinks; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AuthorCopyWith<_Author> get copyWith => __$AuthorCopyWithImpl<_Author>(this, _$identity); + +@override +Map toJson() { + return _$AuthorToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Author&&(identical(other.username, username) || other.username == username)&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.profilePicture, profilePicture) || other.profilePicture == profilePicture)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.location, location) || other.location == location)&&(identical(other.socialMediaLinks, socialMediaLinks) || other.socialMediaLinks == socialMediaLinks)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,username,id,name,profilePicture,bio,location,socialMediaLinks); + +@override +String toString() { + return 'Author(username: $username, id: $id, name: $name, profilePicture: $profilePicture, bio: $bio, location: $location, socialMediaLinks: $socialMediaLinks)'; +} + + +} + +/// @nodoc +abstract mixin class _$AuthorCopyWith<$Res> implements $AuthorCopyWith<$Res> { + factory _$AuthorCopyWith(_Author value, $Res Function(_Author) _then) = __$AuthorCopyWithImpl; +@override @useResult +$Res call({ + String username, String id, String name, String? profilePicture, Bio? bio, String? location, SocialMediaLinks? socialMediaLinks +}); + + +@override $BioCopyWith<$Res>? get bio;@override $SocialMediaLinksCopyWith<$Res>? get socialMediaLinks; + +} +/// @nodoc +class __$AuthorCopyWithImpl<$Res> + implements _$AuthorCopyWith<$Res> { + __$AuthorCopyWithImpl(this._self, this._then); + + final _Author _self; + final $Res Function(_Author) _then; + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? username = null,Object? id = null,Object? name = null,Object? profilePicture = freezed,Object? bio = freezed,Object? location = freezed,Object? socialMediaLinks = freezed,}) { + return _then(_Author( +username: null == username ? _self.username : username // ignore: cast_nullable_to_non_nullable +as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,profilePicture: freezed == profilePicture ? _self.profilePicture : profilePicture // ignore: cast_nullable_to_non_nullable +as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable +as Bio?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable +as String?,socialMediaLinks: freezed == socialMediaLinks ? _self.socialMediaLinks : socialMediaLinks // ignore: cast_nullable_to_non_nullable +as SocialMediaLinks?, + )); +} + +/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$BioCopyWith<$Res>? get bio { + if (_self.bio == null) { + return null; + } + + return $BioCopyWith<$Res>(_self.bio!, (value) { + return _then(_self.copyWith(bio: value)); + }); +}/// Create a copy of Author +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SocialMediaLinksCopyWith<$Res>? get socialMediaLinks { + if (_self.socialMediaLinks == null) { + return null; + } + + return $SocialMediaLinksCopyWith<$Res>(_self.socialMediaLinks!, (value) { + return _then(_self.copyWith(socialMediaLinks: value)); + }); +} +} + +// dart format on diff --git a/mobile-app-new/lib/models/news/author_model.g.dart b/mobile-app-new/lib/models/news/author_model.g.dart new file mode 100644 index 000000000..484aee441 --- /dev/null +++ b/mobile-app-new/lib/models/news/author_model.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'author_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Bio _$BioFromJson(Map json) => + _Bio(text: json['text'] as String?); + +Map _$BioToJson(_Bio instance) => { + 'text': instance.text, +}; + +_SocialMediaLinks _$SocialMediaLinksFromJson(Map json) => + _SocialMediaLinks( + website: json['website'] as String?, + twitter: json['twitter'] as String?, + facebook: json['facebook'] as String?, + ); + +Map _$SocialMediaLinksToJson(_SocialMediaLinks instance) => + { + 'website': instance.website, + 'twitter': instance.twitter, + 'facebook': instance.facebook, + }; + +_Author _$AuthorFromJson(Map json) => _Author( + username: json['username'] as String, + id: json['id'] as String, + name: json['name'] as String, + profilePicture: json['profilePicture'] as String?, + bio: json['bio'] == null + ? null + : Bio.fromJson(json['bio'] as Map), + location: json['location'] as String?, + socialMediaLinks: json['socialMediaLinks'] == null + ? null + : SocialMediaLinks.fromJson( + json['socialMediaLinks'] as Map, + ), +); + +Map _$AuthorToJson(_Author instance) => { + 'username': instance.username, + 'id': instance.id, + 'name': instance.name, + 'profilePicture': instance.profilePicture, + 'bio': instance.bio, + 'location': instance.location, + 'socialMediaLinks': instance.socialMediaLinks, +}; From fe9d2756015f21f04d28ca4f2522c88eb445b8f7 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 28 Jul 2026 06:12:16 +0530 Subject: [PATCH 07/52] feat: post model --- .../{tutorial_model.dart => post_model.dart} | 82 +- .../lib/models/news/post_model.freezed.dart | 1171 +++++++++++++++++ .../lib/models/news/post_model.g.dart | 66 + .../models/news/tutorial_model.freezed.dart | 301 ----- .../lib/models/news/tutorial_model.g.dart | 31 - 5 files changed, 1280 insertions(+), 371 deletions(-) rename mobile-app-new/lib/models/news/{tutorial_model.dart => post_model.dart} (73%) create mode 100644 mobile-app-new/lib/models/news/post_model.freezed.dart create mode 100644 mobile-app-new/lib/models/news/post_model.g.dart delete mode 100644 mobile-app-new/lib/models/news/tutorial_model.freezed.dart delete mode 100644 mobile-app-new/lib/models/news/tutorial_model.g.dart diff --git a/mobile-app-new/lib/models/news/tutorial_model.dart b/mobile-app-new/lib/models/news/post_model.dart similarity index 73% rename from mobile-app-new/lib/models/news/tutorial_model.dart rename to mobile-app-new/lib/models/news/post_model.dart index e373d171c..bf990b9b5 100644 --- a/mobile-app-new/lib/models/news/tutorial_model.dart +++ b/mobile-app-new/lib/models/news/post_model.dart @@ -1,7 +1,8 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'author_model.dart'; -part 'tutorial_model.freezed.dart'; -part 'tutorial_model.g.dart'; +part 'post_model.freezed.dart'; +part 'post_model.g.dart'; // class Tutorial { // final String id; @@ -108,43 +109,46 @@ part 'tutorial_model.g.dart'; // } @freezed -abstract class Author with _$Author { - const factory Author({ +abstract class Tag with _$Tag { + const factory Tag({ + required String id, + required String name, required String slug, + }) = _Tag; + + factory Tag.fromJson(Map json) => _$TagFromJson(json); +} + +@freezed +abstract class CoverImage with _$CoverImage { + const factory CoverImage({required String url}) = _CoverImage; + + factory CoverImage.fromJson(Map json) => + _$CoverImageFromJson(json); +} + +@freezed +abstract class Content with _$Content { + const factory Content({required String html}) = _Content; + + factory Content.fromJson(Map json) => + _$ContentFromJson(json); +} + +@freezed +abstract class Post with _$Post { + const factory Post({ required String id, - required String? name, - required String? profileImage, - required String? bio, - String? website, - String? location, - String? facebook, - String? twitter, - }) = _Author; - - factory Author.fromJson(Map json) => _$AuthorFromJson(json); - - // final String slug; - // final String id; - // final String name; - // final String? profileImage; - // final String? bio; - // // TODO: Below items are not displayed in the UI. To be added - // final String? website; - // final String? location; - // final String? facebook; - // final String? twitter; - - // factory Author.toAuthorFromJson(Map data) { - // return Author( - // slug: data['username'], - // id: data['id'], - // name: data['name'], - // profileImage: data['profilePicture'], - // bio: data['bio']['text'], - // website: data['socialMediaLinks']['website'], - // location: data['location'], - // facebook: data['socialMediaLinks']['facebook'], - // twitter: data['socialMediaLinks']['twitter'], - // ); - // } + required String slug, + required String title, + String? url, + required Author author, + @Default([]) List tags, + CoverImage? coverImage, + required int readTimeInMinutes, + Content? content, + String? publishedAt, + }) = _Post; + + factory Post.fromJson(Map json) => _$PostFromJson(json); } diff --git a/mobile-app-new/lib/models/news/post_model.freezed.dart b/mobile-app-new/lib/models/news/post_model.freezed.dart new file mode 100644 index 000000000..df2fc364f --- /dev/null +++ b/mobile-app-new/lib/models/news/post_model.freezed.dart @@ -0,0 +1,1171 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'post_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Tag { + + String get id; String get name; String get slug; +/// Create a copy of Tag +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$TagCopyWith get copyWith => _$TagCopyWithImpl(this as Tag, _$identity); + + /// Serializes this Tag to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Tag&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.slug, slug) || other.slug == slug)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,name,slug); + +@override +String toString() { + return 'Tag(id: $id, name: $name, slug: $slug)'; +} + + +} + +/// @nodoc +abstract mixin class $TagCopyWith<$Res> { + factory $TagCopyWith(Tag value, $Res Function(Tag) _then) = _$TagCopyWithImpl; +@useResult +$Res call({ + String id, String name, String slug +}); + + + + +} +/// @nodoc +class _$TagCopyWithImpl<$Res> + implements $TagCopyWith<$Res> { + _$TagCopyWithImpl(this._self, this._then); + + final Tag _self; + final $Res Function(Tag) _then; + +/// Create a copy of Tag +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? slug = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Tag]. +extension TagPatterns on Tag { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Tag value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Tag() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Tag value) $default,){ +final _that = this; +switch (_that) { +case _Tag(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Tag value)? $default,){ +final _that = this; +switch (_that) { +case _Tag() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String slug)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Tag() when $default != null: +return $default(_that.id,_that.name,_that.slug);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String name, String slug) $default,) {final _that = this; +switch (_that) { +case _Tag(): +return $default(_that.id,_that.name,_that.slug);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String slug)? $default,) {final _that = this; +switch (_that) { +case _Tag() when $default != null: +return $default(_that.id,_that.name,_that.slug);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Tag implements Tag { + const _Tag({required this.id, required this.name, required this.slug}); + factory _Tag.fromJson(Map json) => _$TagFromJson(json); + +@override final String id; +@override final String name; +@override final String slug; + +/// Create a copy of Tag +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$TagCopyWith<_Tag> get copyWith => __$TagCopyWithImpl<_Tag>(this, _$identity); + +@override +Map toJson() { + return _$TagToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Tag&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.slug, slug) || other.slug == slug)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,name,slug); + +@override +String toString() { + return 'Tag(id: $id, name: $name, slug: $slug)'; +} + + +} + +/// @nodoc +abstract mixin class _$TagCopyWith<$Res> implements $TagCopyWith<$Res> { + factory _$TagCopyWith(_Tag value, $Res Function(_Tag) _then) = __$TagCopyWithImpl; +@override @useResult +$Res call({ + String id, String name, String slug +}); + + + + +} +/// @nodoc +class __$TagCopyWithImpl<$Res> + implements _$TagCopyWith<$Res> { + __$TagCopyWithImpl(this._self, this._then); + + final _Tag _self; + final $Res Function(_Tag) _then; + +/// Create a copy of Tag +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? slug = null,}) { + return _then(_Tag( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$CoverImage { + + String get url; +/// Create a copy of CoverImage +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CoverImageCopyWith get copyWith => _$CoverImageCopyWithImpl(this as CoverImage, _$identity); + + /// Serializes this CoverImage to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CoverImage&&(identical(other.url, url) || other.url == url)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,url); + +@override +String toString() { + return 'CoverImage(url: $url)'; +} + + +} + +/// @nodoc +abstract mixin class $CoverImageCopyWith<$Res> { + factory $CoverImageCopyWith(CoverImage value, $Res Function(CoverImage) _then) = _$CoverImageCopyWithImpl; +@useResult +$Res call({ + String url +}); + + + + +} +/// @nodoc +class _$CoverImageCopyWithImpl<$Res> + implements $CoverImageCopyWith<$Res> { + _$CoverImageCopyWithImpl(this._self, this._then); + + final CoverImage _self; + final $Res Function(CoverImage) _then; + +/// Create a copy of CoverImage +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? url = null,}) { + return _then(_self.copyWith( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CoverImage]. +extension CoverImagePatterns on CoverImage { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CoverImage value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CoverImage() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CoverImage value) $default,){ +final _that = this; +switch (_that) { +case _CoverImage(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CoverImage value)? $default,){ +final _that = this; +switch (_that) { +case _CoverImage() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String url)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CoverImage() when $default != null: +return $default(_that.url);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String url) $default,) {final _that = this; +switch (_that) { +case _CoverImage(): +return $default(_that.url);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String url)? $default,) {final _that = this; +switch (_that) { +case _CoverImage() when $default != null: +return $default(_that.url);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CoverImage implements CoverImage { + const _CoverImage({required this.url}); + factory _CoverImage.fromJson(Map json) => _$CoverImageFromJson(json); + +@override final String url; + +/// Create a copy of CoverImage +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CoverImageCopyWith<_CoverImage> get copyWith => __$CoverImageCopyWithImpl<_CoverImage>(this, _$identity); + +@override +Map toJson() { + return _$CoverImageToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CoverImage&&(identical(other.url, url) || other.url == url)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,url); + +@override +String toString() { + return 'CoverImage(url: $url)'; +} + + +} + +/// @nodoc +abstract mixin class _$CoverImageCopyWith<$Res> implements $CoverImageCopyWith<$Res> { + factory _$CoverImageCopyWith(_CoverImage value, $Res Function(_CoverImage) _then) = __$CoverImageCopyWithImpl; +@override @useResult +$Res call({ + String url +}); + + + + +} +/// @nodoc +class __$CoverImageCopyWithImpl<$Res> + implements _$CoverImageCopyWith<$Res> { + __$CoverImageCopyWithImpl(this._self, this._then); + + final _CoverImage _self; + final $Res Function(_CoverImage) _then; + +/// Create a copy of CoverImage +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? url = null,}) { + return _then(_CoverImage( +url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$Content { + + String get html; +/// Create a copy of Content +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ContentCopyWith get copyWith => _$ContentCopyWithImpl(this as Content, _$identity); + + /// Serializes this Content to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Content&&(identical(other.html, html) || other.html == html)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,html); + +@override +String toString() { + return 'Content(html: $html)'; +} + + +} + +/// @nodoc +abstract mixin class $ContentCopyWith<$Res> { + factory $ContentCopyWith(Content value, $Res Function(Content) _then) = _$ContentCopyWithImpl; +@useResult +$Res call({ + String html +}); + + + + +} +/// @nodoc +class _$ContentCopyWithImpl<$Res> + implements $ContentCopyWith<$Res> { + _$ContentCopyWithImpl(this._self, this._then); + + final Content _self; + final $Res Function(Content) _then; + +/// Create a copy of Content +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? html = null,}) { + return _then(_self.copyWith( +html: null == html ? _self.html : html // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Content]. +extension ContentPatterns on Content { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Content value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Content() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Content value) $default,){ +final _that = this; +switch (_that) { +case _Content(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Content value)? $default,){ +final _that = this; +switch (_that) { +case _Content() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String html)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Content() when $default != null: +return $default(_that.html);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String html) $default,) {final _that = this; +switch (_that) { +case _Content(): +return $default(_that.html);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String html)? $default,) {final _that = this; +switch (_that) { +case _Content() when $default != null: +return $default(_that.html);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Content implements Content { + const _Content({required this.html}); + factory _Content.fromJson(Map json) => _$ContentFromJson(json); + +@override final String html; + +/// Create a copy of Content +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ContentCopyWith<_Content> get copyWith => __$ContentCopyWithImpl<_Content>(this, _$identity); + +@override +Map toJson() { + return _$ContentToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Content&&(identical(other.html, html) || other.html == html)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,html); + +@override +String toString() { + return 'Content(html: $html)'; +} + + +} + +/// @nodoc +abstract mixin class _$ContentCopyWith<$Res> implements $ContentCopyWith<$Res> { + factory _$ContentCopyWith(_Content value, $Res Function(_Content) _then) = __$ContentCopyWithImpl; +@override @useResult +$Res call({ + String html +}); + + + + +} +/// @nodoc +class __$ContentCopyWithImpl<$Res> + implements _$ContentCopyWith<$Res> { + __$ContentCopyWithImpl(this._self, this._then); + + final _Content _self; + final $Res Function(_Content) _then; + +/// Create a copy of Content +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? html = null,}) { + return _then(_Content( +html: null == html ? _self.html : html // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$Post { + + String get id; String get slug; String get title; String? get url; Author get author; List get tags; CoverImage? get coverImage; int get readTimeInMinutes; Content? get content; String? get publishedAt; +/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PostCopyWith get copyWith => _$PostCopyWithImpl(this as Post, _$identity); + + /// Serializes this Post to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Post&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.readTimeInMinutes, readTimeInMinutes) || other.readTimeInMinutes == readTimeInMinutes)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,slug,title,url,author,const DeepCollectionEquality().hash(tags),coverImage,readTimeInMinutes,content,publishedAt); + +@override +String toString() { + return 'Post(id: $id, slug: $slug, title: $title, url: $url, author: $author, tags: $tags, coverImage: $coverImage, readTimeInMinutes: $readTimeInMinutes, content: $content, publishedAt: $publishedAt)'; +} + + +} + +/// @nodoc +abstract mixin class $PostCopyWith<$Res> { + factory $PostCopyWith(Post value, $Res Function(Post) _then) = _$PostCopyWithImpl; +@useResult +$Res call({ + String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt +}); + + +$AuthorCopyWith<$Res> get author;$CoverImageCopyWith<$Res>? get coverImage;$ContentCopyWith<$Res>? get content; + +} +/// @nodoc +class _$PostCopyWithImpl<$Res> + implements $PostCopyWith<$Res> { + _$PostCopyWithImpl(this._self, this._then); + + final Post _self; + final $Res Function(Post) _then; + +/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? url = freezed,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? content = freezed,Object? publishedAt = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as Author,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable +as List,coverImage: freezed == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable +as CoverImage?,readTimeInMinutes: null == readTimeInMinutes ? _self.readTimeInMinutes : readTimeInMinutes // ignore: cast_nullable_to_non_nullable +as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as Content?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String?, + )); +} +/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AuthorCopyWith<$Res> get author { + + return $AuthorCopyWith<$Res>(_self.author, (value) { + return _then(_self.copyWith(author: value)); + }); +}/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CoverImageCopyWith<$Res>? get coverImage { + if (_self.coverImage == null) { + return null; + } + + return $CoverImageCopyWith<$Res>(_self.coverImage!, (value) { + return _then(_self.copyWith(coverImage: value)); + }); +}/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ContentCopyWith<$Res>? get content { + if (_self.content == null) { + return null; + } + + return $ContentCopyWith<$Res>(_self.content!, (value) { + return _then(_self.copyWith(content: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [Post]. +extension PostPatterns on Post { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Post value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Post() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Post value) $default,){ +final _that = this; +switch (_that) { +case _Post(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Post value)? $default,){ +final _that = this; +switch (_that) { +case _Post() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Post() when $default != null: +return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.content,_that.publishedAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt) $default,) {final _that = this; +switch (_that) { +case _Post(): +return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.content,_that.publishedAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt)? $default,) {final _that = this; +switch (_that) { +case _Post() when $default != null: +return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.content,_that.publishedAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Post implements Post { + const _Post({required this.id, required this.slug, required this.title, this.url, required this.author, final List tags = const [], this.coverImage, required this.readTimeInMinutes, this.content, this.publishedAt}): _tags = tags; + factory _Post.fromJson(Map json) => _$PostFromJson(json); + +@override final String id; +@override final String slug; +@override final String title; +@override final String? url; +@override final Author author; + final List _tags; +@override@JsonKey() List get tags { + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tags); +} + +@override final CoverImage? coverImage; +@override final int readTimeInMinutes; +@override final Content? content; +@override final String? publishedAt; + +/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PostCopyWith<_Post> get copyWith => __$PostCopyWithImpl<_Post>(this, _$identity); + +@override +Map toJson() { + return _$PostToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Post&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.readTimeInMinutes, readTimeInMinutes) || other.readTimeInMinutes == readTimeInMinutes)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,slug,title,url,author,const DeepCollectionEquality().hash(_tags),coverImage,readTimeInMinutes,content,publishedAt); + +@override +String toString() { + return 'Post(id: $id, slug: $slug, title: $title, url: $url, author: $author, tags: $tags, coverImage: $coverImage, readTimeInMinutes: $readTimeInMinutes, content: $content, publishedAt: $publishedAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$PostCopyWith<$Res> implements $PostCopyWith<$Res> { + factory _$PostCopyWith(_Post value, $Res Function(_Post) _then) = __$PostCopyWithImpl; +@override @useResult +$Res call({ + String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt +}); + + +@override $AuthorCopyWith<$Res> get author;@override $CoverImageCopyWith<$Res>? get coverImage;@override $ContentCopyWith<$Res>? get content; + +} +/// @nodoc +class __$PostCopyWithImpl<$Res> + implements _$PostCopyWith<$Res> { + __$PostCopyWithImpl(this._self, this._then); + + final _Post _self; + final $Res Function(_Post) _then; + +/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? url = freezed,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? content = freezed,Object? publishedAt = freezed,}) { + return _then(_Post( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as Author,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable +as List,coverImage: freezed == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable +as CoverImage?,readTimeInMinutes: null == readTimeInMinutes ? _self.readTimeInMinutes : readTimeInMinutes // ignore: cast_nullable_to_non_nullable +as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as Content?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AuthorCopyWith<$Res> get author { + + return $AuthorCopyWith<$Res>(_self.author, (value) { + return _then(_self.copyWith(author: value)); + }); +}/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CoverImageCopyWith<$Res>? get coverImage { + if (_self.coverImage == null) { + return null; + } + + return $CoverImageCopyWith<$Res>(_self.coverImage!, (value) { + return _then(_self.copyWith(coverImage: value)); + }); +}/// Create a copy of Post +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ContentCopyWith<$Res>? get content { + if (_self.content == null) { + return null; + } + + return $ContentCopyWith<$Res>(_self.content!, (value) { + return _then(_self.copyWith(content: value)); + }); +} +} + +// dart format on diff --git a/mobile-app-new/lib/models/news/post_model.g.dart b/mobile-app-new/lib/models/news/post_model.g.dart new file mode 100644 index 000000000..b7daae666 --- /dev/null +++ b/mobile-app-new/lib/models/news/post_model.g.dart @@ -0,0 +1,66 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'post_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Tag _$TagFromJson(Map json) => _Tag( + id: json['id'] as String, + name: json['name'] as String, + slug: json['slug'] as String, +); + +Map _$TagToJson(_Tag instance) => { + 'id': instance.id, + 'name': instance.name, + 'slug': instance.slug, +}; + +_CoverImage _$CoverImageFromJson(Map json) => + _CoverImage(url: json['url'] as String); + +Map _$CoverImageToJson(_CoverImage instance) => + {'url': instance.url}; + +_Content _$ContentFromJson(Map json) => + _Content(html: json['html'] as String); + +Map _$ContentToJson(_Content instance) => { + 'html': instance.html, +}; + +_Post _$PostFromJson(Map json) => _Post( + id: json['id'] as String, + slug: json['slug'] as String, + title: json['title'] as String, + url: json['url'] as String?, + author: Author.fromJson(json['author'] as Map), + tags: + (json['tags'] as List?) + ?.map((e) => Tag.fromJson(e as Map)) + .toList() ?? + const [], + coverImage: json['coverImage'] == null + ? null + : CoverImage.fromJson(json['coverImage'] as Map), + readTimeInMinutes: (json['readTimeInMinutes'] as num).toInt(), + content: json['content'] == null + ? null + : Content.fromJson(json['content'] as Map), + publishedAt: json['publishedAt'] as String?, +); + +Map _$PostToJson(_Post instance) => { + 'id': instance.id, + 'slug': instance.slug, + 'title': instance.title, + 'url': instance.url, + 'author': instance.author, + 'tags': instance.tags, + 'coverImage': instance.coverImage, + 'readTimeInMinutes': instance.readTimeInMinutes, + 'content': instance.content, + 'publishedAt': instance.publishedAt, +}; diff --git a/mobile-app-new/lib/models/news/tutorial_model.freezed.dart b/mobile-app-new/lib/models/news/tutorial_model.freezed.dart deleted file mode 100644 index a8a938af9..000000000 --- a/mobile-app-new/lib/models/news/tutorial_model.freezed.dart +++ /dev/null @@ -1,301 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'tutorial_model.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; - -/// @nodoc -mixin _$Author { - - String get slug; String get id; String? get name; String? get profileImage; String? get bio; String? get website; String? get location; String? get facebook; String? get twitter; -/// Create a copy of Author -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$AuthorCopyWith get copyWith => _$AuthorCopyWithImpl(this as Author, _$identity); - - /// Serializes this Author to a JSON map. - Map toJson(); - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is Author&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.website, website) || other.website == website)&&(identical(other.location, location) || other.location == location)&&(identical(other.facebook, facebook) || other.facebook == facebook)&&(identical(other.twitter, twitter) || other.twitter == twitter)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode => Object.hash(runtimeType,slug,id,name,profileImage,bio,website,location,facebook,twitter); - -@override -String toString() { - return 'Author(slug: $slug, id: $id, name: $name, profileImage: $profileImage, bio: $bio, website: $website, location: $location, facebook: $facebook, twitter: $twitter)'; -} - - -} - -/// @nodoc -abstract mixin class $AuthorCopyWith<$Res> { - factory $AuthorCopyWith(Author value, $Res Function(Author) _then) = _$AuthorCopyWithImpl; -@useResult -$Res call({ - String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter -}); - - - - -} -/// @nodoc -class _$AuthorCopyWithImpl<$Res> - implements $AuthorCopyWith<$Res> { - _$AuthorCopyWithImpl(this._self, this._then); - - final Author _self; - final $Res Function(Author) _then; - -/// Create a copy of Author -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? slug = null,Object? id = null,Object? name = freezed,Object? profileImage = freezed,Object? bio = freezed,Object? website = freezed,Object? location = freezed,Object? facebook = freezed,Object? twitter = freezed,}) { - return _then(_self.copyWith( -slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable -as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?,profileImage: freezed == profileImage ? _self.profileImage : profileImage // ignore: cast_nullable_to_non_nullable -as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable -as String?,website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable -as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable -as String?,facebook: freezed == facebook ? _self.facebook : facebook // ignore: cast_nullable_to_non_nullable -as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [Author]. -extension AuthorPatterns on Author { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _Author value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _Author() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _Author value) $default,){ -final _that = this; -switch (_that) { -case _Author(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Author value)? $default,){ -final _that = this; -switch (_that) { -case _Author() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _Author() when $default != null: -return $default(_that.slug,_that.id,_that.name,_that.profileImage,_that.bio,_that.website,_that.location,_that.facebook,_that.twitter);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter) $default,) {final _that = this; -switch (_that) { -case _Author(): -return $default(_that.slug,_that.id,_that.name,_that.profileImage,_that.bio,_that.website,_that.location,_that.facebook,_that.twitter);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter)? $default,) {final _that = this; -switch (_that) { -case _Author() when $default != null: -return $default(_that.slug,_that.id,_that.name,_that.profileImage,_that.bio,_that.website,_that.location,_that.facebook,_that.twitter);case _: - return null; - -} -} - -} - -/// @nodoc -@JsonSerializable() - -class _Author implements Author { - const _Author({required this.slug, required this.id, required this.name, required this.profileImage, required this.bio, this.website, this.location, this.facebook, this.twitter}); - factory _Author.fromJson(Map json) => _$AuthorFromJson(json); - -@override final String slug; -@override final String id; -@override final String? name; -@override final String? profileImage; -@override final String? bio; -@override final String? website; -@override final String? location; -@override final String? facebook; -@override final String? twitter; - -/// Create a copy of Author -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$AuthorCopyWith<_Author> get copyWith => __$AuthorCopyWithImpl<_Author>(this, _$identity); - -@override -Map toJson() { - return _$AuthorToJson(this, ); -} - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Author&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.website, website) || other.website == website)&&(identical(other.location, location) || other.location == location)&&(identical(other.facebook, facebook) || other.facebook == facebook)&&(identical(other.twitter, twitter) || other.twitter == twitter)); -} - -@JsonKey(includeFromJson: false, includeToJson: false) -@override -int get hashCode => Object.hash(runtimeType,slug,id,name,profileImage,bio,website,location,facebook,twitter); - -@override -String toString() { - return 'Author(slug: $slug, id: $id, name: $name, profileImage: $profileImage, bio: $bio, website: $website, location: $location, facebook: $facebook, twitter: $twitter)'; -} - - -} - -/// @nodoc -abstract mixin class _$AuthorCopyWith<$Res> implements $AuthorCopyWith<$Res> { - factory _$AuthorCopyWith(_Author value, $Res Function(_Author) _then) = __$AuthorCopyWithImpl; -@override @useResult -$Res call({ - String slug, String id, String? name, String? profileImage, String? bio, String? website, String? location, String? facebook, String? twitter -}); - - - - -} -/// @nodoc -class __$AuthorCopyWithImpl<$Res> - implements _$AuthorCopyWith<$Res> { - __$AuthorCopyWithImpl(this._self, this._then); - - final _Author _self; - final $Res Function(_Author) _then; - -/// Create a copy of Author -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? slug = null,Object? id = null,Object? name = freezed,Object? profileImage = freezed,Object? bio = freezed,Object? website = freezed,Object? location = freezed,Object? facebook = freezed,Object? twitter = freezed,}) { - return _then(_Author( -slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable -as String,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String?,profileImage: freezed == profileImage ? _self.profileImage : profileImage // ignore: cast_nullable_to_non_nullable -as String?,bio: freezed == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable -as String?,website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable -as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable -as String?,facebook: freezed == facebook ? _self.facebook : facebook // ignore: cast_nullable_to_non_nullable -as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - -// dart format on diff --git a/mobile-app-new/lib/models/news/tutorial_model.g.dart b/mobile-app-new/lib/models/news/tutorial_model.g.dart deleted file mode 100644 index 3fc7544b9..000000000 --- a/mobile-app-new/lib/models/news/tutorial_model.g.dart +++ /dev/null @@ -1,31 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'tutorial_model.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_Author _$AuthorFromJson(Map json) => _Author( - slug: json['slug'] as String, - id: json['id'] as String, - name: json['name'] as String?, - profileImage: json['profileImage'] as String?, - bio: json['bio'] as String?, - website: json['website'] as String?, - location: json['location'] as String?, - facebook: json['facebook'] as String?, - twitter: json['twitter'] as String?, -); - -Map _$AuthorToJson(_Author instance) => { - 'slug': instance.slug, - 'id': instance.id, - 'name': instance.name, - 'profileImage': instance.profileImage, - 'bio': instance.bio, - 'website': instance.website, - 'location': instance.location, - 'facebook': instance.facebook, - 'twitter': instance.twitter, -}; From 3bc5d22119d3c47bac3d357feb3aadc43a396c53 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 28 Jul 2026 06:18:32 +0530 Subject: [PATCH 08/52] fix: make post and author model inline with Hashnode GQL schema --- .../lib/models/news/author_model.dart | 2 +- .../lib/models/news/author_model.freezed.dart | 28 +++--- .../lib/models/news/author_model.g.dart | 2 +- .../lib/models/news/post_model.dart | 8 +- .../lib/models/news/post_model.freezed.dart | 94 +++++++++---------- .../lib/models/news/post_model.g.dart | 10 +- 6 files changed, 68 insertions(+), 76 deletions(-) diff --git a/mobile-app-new/lib/models/news/author_model.dart b/mobile-app-new/lib/models/news/author_model.dart index fb3904d84..6f46da637 100644 --- a/mobile-app-new/lib/models/news/author_model.dart +++ b/mobile-app-new/lib/models/news/author_model.dart @@ -5,7 +5,7 @@ part 'author_model.g.dart'; @freezed abstract class Bio with _$Bio { - const factory Bio({String? text}) = _Bio; + const factory Bio({required String text}) = _Bio; factory Bio.fromJson(Map json) => _$BioFromJson(json); } diff --git a/mobile-app-new/lib/models/news/author_model.freezed.dart b/mobile-app-new/lib/models/news/author_model.freezed.dart index c5886abfb..44e37478a 100644 --- a/mobile-app-new/lib/models/news/author_model.freezed.dart +++ b/mobile-app-new/lib/models/news/author_model.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$Bio { - String? get text; + String get text; /// Create a copy of Bio /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -48,7 +48,7 @@ abstract mixin class $BioCopyWith<$Res> { factory $BioCopyWith(Bio value, $Res Function(Bio) _then) = _$BioCopyWithImpl; @useResult $Res call({ - String? text + String text }); @@ -65,10 +65,10 @@ class _$BioCopyWithImpl<$Res> /// Create a copy of Bio /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? text = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? text = null,}) { return _then(_self.copyWith( -text: freezed == text ? _self.text : text // ignore: cast_nullable_to_non_nullable -as String?, +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String, )); } @@ -153,7 +153,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String? text)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String text)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _Bio() when $default != null: return $default(_that.text);case _: @@ -174,7 +174,7 @@ return $default(_that.text);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String? text) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String text) $default,) {final _that = this; switch (_that) { case _Bio(): return $default(_that.text);case _: @@ -194,7 +194,7 @@ return $default(_that.text);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? text)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String text)? $default,) {final _that = this; switch (_that) { case _Bio() when $default != null: return $default(_that.text);case _: @@ -209,10 +209,10 @@ return $default(_that.text);case _: @JsonSerializable() class _Bio implements Bio { - const _Bio({this.text}); + const _Bio({required this.text}); factory _Bio.fromJson(Map json) => _$BioFromJson(json); -@override final String? text; +@override final String text; /// Create a copy of Bio /// with the given fields replaced by the non-null parameter values. @@ -247,7 +247,7 @@ abstract mixin class _$BioCopyWith<$Res> implements $BioCopyWith<$Res> { factory _$BioCopyWith(_Bio value, $Res Function(_Bio) _then) = __$BioCopyWithImpl; @override @useResult $Res call({ - String? text + String text }); @@ -264,10 +264,10 @@ class __$BioCopyWithImpl<$Res> /// Create a copy of Bio /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? text = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? text = null,}) { return _then(_Bio( -text: freezed == text ? _self.text : text // ignore: cast_nullable_to_non_nullable -as String?, +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String, )); } diff --git a/mobile-app-new/lib/models/news/author_model.g.dart b/mobile-app-new/lib/models/news/author_model.g.dart index 484aee441..c8ee95bb3 100644 --- a/mobile-app-new/lib/models/news/author_model.g.dart +++ b/mobile-app-new/lib/models/news/author_model.g.dart @@ -7,7 +7,7 @@ part of 'author_model.dart'; // ************************************************************************** _Bio _$BioFromJson(Map json) => - _Bio(text: json['text'] as String?); + _Bio(text: json['text'] as String); Map _$BioToJson(_Bio instance) => { 'text': instance.text, diff --git a/mobile-app-new/lib/models/news/post_model.dart b/mobile-app-new/lib/models/news/post_model.dart index bf990b9b5..cdb3d4c14 100644 --- a/mobile-app-new/lib/models/news/post_model.dart +++ b/mobile-app-new/lib/models/news/post_model.dart @@ -121,7 +121,7 @@ abstract class Tag with _$Tag { @freezed abstract class CoverImage with _$CoverImage { - const factory CoverImage({required String url}) = _CoverImage; + const factory CoverImage({String? url}) = _CoverImage; factory CoverImage.fromJson(Map json) => _$CoverImageFromJson(json); @@ -141,13 +141,13 @@ abstract class Post with _$Post { required String id, required String slug, required String title, - String? url, + required String url, required Author author, @Default([]) List tags, CoverImage? coverImage, required int readTimeInMinutes, - Content? content, - String? publishedAt, + required Content content, + required String publishedAt, }) = _Post; factory Post.fromJson(Map json) => _$PostFromJson(json); diff --git a/mobile-app-new/lib/models/news/post_model.freezed.dart b/mobile-app-new/lib/models/news/post_model.freezed.dart index df2fc364f..4772f117c 100644 --- a/mobile-app-new/lib/models/news/post_model.freezed.dart +++ b/mobile-app-new/lib/models/news/post_model.freezed.dart @@ -284,7 +284,7 @@ as String, /// @nodoc mixin _$CoverImage { - String get url; + String? get url; /// Create a copy of CoverImage /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -317,7 +317,7 @@ abstract mixin class $CoverImageCopyWith<$Res> { factory $CoverImageCopyWith(CoverImage value, $Res Function(CoverImage) _then) = _$CoverImageCopyWithImpl; @useResult $Res call({ - String url + String? url }); @@ -334,10 +334,10 @@ class _$CoverImageCopyWithImpl<$Res> /// Create a copy of CoverImage /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? url = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? url = freezed,}) { return _then(_self.copyWith( -url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable -as String, +url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?, )); } @@ -422,7 +422,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String url)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String? url)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _CoverImage() when $default != null: return $default(_that.url);case _: @@ -443,7 +443,7 @@ return $default(_that.url);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String url) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String? url) $default,) {final _that = this; switch (_that) { case _CoverImage(): return $default(_that.url);case _: @@ -463,7 +463,7 @@ return $default(_that.url);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String url)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? url)? $default,) {final _that = this; switch (_that) { case _CoverImage() when $default != null: return $default(_that.url);case _: @@ -478,10 +478,10 @@ return $default(_that.url);case _: @JsonSerializable() class _CoverImage implements CoverImage { - const _CoverImage({required this.url}); + const _CoverImage({this.url}); factory _CoverImage.fromJson(Map json) => _$CoverImageFromJson(json); -@override final String url; +@override final String? url; /// Create a copy of CoverImage /// with the given fields replaced by the non-null parameter values. @@ -516,7 +516,7 @@ abstract mixin class _$CoverImageCopyWith<$Res> implements $CoverImageCopyWith<$ factory _$CoverImageCopyWith(_CoverImage value, $Res Function(_CoverImage) _then) = __$CoverImageCopyWithImpl; @override @useResult $Res call({ - String url + String? url }); @@ -533,10 +533,10 @@ class __$CoverImageCopyWithImpl<$Res> /// Create a copy of CoverImage /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? url = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? url = freezed,}) { return _then(_CoverImage( -url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable -as String, +url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?, )); } @@ -810,7 +810,7 @@ as String, /// @nodoc mixin _$Post { - String get id; String get slug; String get title; String? get url; Author get author; List get tags; CoverImage? get coverImage; int get readTimeInMinutes; Content? get content; String? get publishedAt; + String get id; String get slug; String get title; String get url; Author get author; List get tags; CoverImage? get coverImage; int get readTimeInMinutes; Content get content; String get publishedAt; /// Create a copy of Post /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -843,11 +843,11 @@ abstract mixin class $PostCopyWith<$Res> { factory $PostCopyWith(Post value, $Res Function(Post) _then) = _$PostCopyWithImpl; @useResult $Res call({ - String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt + String id, String slug, String title, String url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content content, String publishedAt }); -$AuthorCopyWith<$Res> get author;$CoverImageCopyWith<$Res>? get coverImage;$ContentCopyWith<$Res>? get content; +$AuthorCopyWith<$Res> get author;$CoverImageCopyWith<$Res>? get coverImage;$ContentCopyWith<$Res> get content; } /// @nodoc @@ -860,19 +860,19 @@ class _$PostCopyWithImpl<$Res> /// Create a copy of Post /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? url = freezed,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? content = freezed,Object? publishedAt = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? url = null,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? content = null,Object? publishedAt = null,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable -as String,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable -as String?,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable as Author,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable as List,coverImage: freezed == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable as CoverImage?,readTimeInMinutes: null == readTimeInMinutes ? _self.readTimeInMinutes : readTimeInMinutes // ignore: cast_nullable_to_non_nullable -as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable -as Content?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable -as String?, +as int,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as Content,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String, )); } /// Create a copy of Post @@ -900,12 +900,9 @@ $CoverImageCopyWith<$Res>? get coverImage { /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$ContentCopyWith<$Res>? get content { - if (_self.content == null) { - return null; - } - - return $ContentCopyWith<$Res>(_self.content!, (value) { +$ContentCopyWith<$Res> get content { + + return $ContentCopyWith<$Res>(_self.content, (value) { return _then(_self.copyWith(content: value)); }); } @@ -990,7 +987,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String slug, String title, String url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content content, String publishedAt)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _Post() when $default != null: return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.content,_that.publishedAt);case _: @@ -1011,7 +1008,7 @@ return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tag /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String slug, String title, String url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content content, String publishedAt) $default,) {final _that = this; switch (_that) { case _Post(): return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.content,_that.publishedAt);case _: @@ -1031,7 +1028,7 @@ return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tag /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String slug, String title, String url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content content, String publishedAt)? $default,) {final _that = this; switch (_that) { case _Post() when $default != null: return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.content,_that.publishedAt);case _: @@ -1046,13 +1043,13 @@ return $default(_that.id,_that.slug,_that.title,_that.url,_that.author,_that.tag @JsonSerializable() class _Post implements Post { - const _Post({required this.id, required this.slug, required this.title, this.url, required this.author, final List tags = const [], this.coverImage, required this.readTimeInMinutes, this.content, this.publishedAt}): _tags = tags; + const _Post({required this.id, required this.slug, required this.title, required this.url, required this.author, final List tags = const [], this.coverImage, required this.readTimeInMinutes, required this.content, required this.publishedAt}): _tags = tags; factory _Post.fromJson(Map json) => _$PostFromJson(json); @override final String id; @override final String slug; @override final String title; -@override final String? url; +@override final String url; @override final Author author; final List _tags; @override@JsonKey() List get tags { @@ -1063,8 +1060,8 @@ class _Post implements Post { @override final CoverImage? coverImage; @override final int readTimeInMinutes; -@override final Content? content; -@override final String? publishedAt; +@override final Content content; +@override final String publishedAt; /// Create a copy of Post /// with the given fields replaced by the non-null parameter values. @@ -1099,11 +1096,11 @@ abstract mixin class _$PostCopyWith<$Res> implements $PostCopyWith<$Res> { factory _$PostCopyWith(_Post value, $Res Function(_Post) _then) = __$PostCopyWithImpl; @override @useResult $Res call({ - String id, String slug, String title, String? url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content? content, String? publishedAt + String id, String slug, String title, String url, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, Content content, String publishedAt }); -@override $AuthorCopyWith<$Res> get author;@override $CoverImageCopyWith<$Res>? get coverImage;@override $ContentCopyWith<$Res>? get content; +@override $AuthorCopyWith<$Res> get author;@override $CoverImageCopyWith<$Res>? get coverImage;@override $ContentCopyWith<$Res> get content; } /// @nodoc @@ -1116,19 +1113,19 @@ class __$PostCopyWithImpl<$Res> /// Create a copy of Post /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? url = freezed,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? content = freezed,Object? publishedAt = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? url = null,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? content = null,Object? publishedAt = null,}) { return _then(_Post( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable -as String,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable -as String?,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable as Author,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable as List,coverImage: freezed == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable as CoverImage?,readTimeInMinutes: null == readTimeInMinutes ? _self.readTimeInMinutes : readTimeInMinutes // ignore: cast_nullable_to_non_nullable -as int,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable -as Content?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable -as String?, +as int,content: null == content ? _self.content : content // ignore: cast_nullable_to_non_nullable +as Content,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String, )); } @@ -1157,12 +1154,9 @@ $CoverImageCopyWith<$Res>? get coverImage { /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$ContentCopyWith<$Res>? get content { - if (_self.content == null) { - return null; - } - - return $ContentCopyWith<$Res>(_self.content!, (value) { +$ContentCopyWith<$Res> get content { + + return $ContentCopyWith<$Res>(_self.content, (value) { return _then(_self.copyWith(content: value)); }); } diff --git a/mobile-app-new/lib/models/news/post_model.g.dart b/mobile-app-new/lib/models/news/post_model.g.dart index b7daae666..23679acc2 100644 --- a/mobile-app-new/lib/models/news/post_model.g.dart +++ b/mobile-app-new/lib/models/news/post_model.g.dart @@ -19,7 +19,7 @@ Map _$TagToJson(_Tag instance) => { }; _CoverImage _$CoverImageFromJson(Map json) => - _CoverImage(url: json['url'] as String); + _CoverImage(url: json['url'] as String?); Map _$CoverImageToJson(_CoverImage instance) => {'url': instance.url}; @@ -35,7 +35,7 @@ _Post _$PostFromJson(Map json) => _Post( id: json['id'] as String, slug: json['slug'] as String, title: json['title'] as String, - url: json['url'] as String?, + url: json['url'] as String, author: Author.fromJson(json['author'] as Map), tags: (json['tags'] as List?) @@ -46,10 +46,8 @@ _Post _$PostFromJson(Map json) => _Post( ? null : CoverImage.fromJson(json['coverImage'] as Map), readTimeInMinutes: (json['readTimeInMinutes'] as num).toInt(), - content: json['content'] == null - ? null - : Content.fromJson(json['content'] as Map), - publishedAt: json['publishedAt'] as String?, + content: Content.fromJson(json['content'] as Map), + publishedAt: json['publishedAt'] as String, ); Map _$PostToJson(_Post instance) => { From 0b95ec295c7ce654b86014a5f6c25ee7ea7f6eda Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 29 Jul 2026 07:28:26 +0530 Subject: [PATCH 09/52] ctrl + s ft riverpod repos and svcs --- .../lib/repositories/news_api_repository.dart | 123 ++++--- .../repositories/news_api_repository.g.dart | 57 ++++ .../lib/services/news/api_service.dart | 301 +++--------------- .../lib/services/news/api_service.g.dart | 51 +++ 4 files changed, 215 insertions(+), 317 deletions(-) create mode 100644 mobile-app-new/lib/repositories/news_api_repository.g.dart create mode 100644 mobile-app-new/lib/services/news/api_service.g.dart diff --git a/mobile-app-new/lib/repositories/news_api_repository.dart b/mobile-app-new/lib/repositories/news_api_repository.dart index 7fe436711..a0b060755 100644 --- a/mobile-app-new/lib/repositories/news_api_repository.dart +++ b/mobile-app-new/lib/repositories/news_api_repository.dart @@ -1,10 +1,16 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:graphql/client.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -typedef ApiData = ({String endCursor, bool hasNextPage, List posts}); -typedef GetAllPostsT = Future; +part 'news_api_repository.g.dart'; -const apiUrl = 'https://gql-beta.hashnode.com'; +typedef RawPaginatedResponse = ({ + String endCursor, + bool hasNextPage, + List> items, +}); + +const _apiUrl = 'https://gql-beta.hashnode.com'; const postsPerPage = 20; @@ -37,20 +43,15 @@ const postFieldsFragment = r''' coverImage { url } - brief readTimeInMinutes content { html } - seo { - description - } publishedAt - updatedAt } '''; -const getAllPostsQuery = +const _getAllPostsQuery = postFieldsFragment + r''' query GetAllPosts($publicationId: ObjectId!, $first: Int!, $after: String) { @@ -71,7 +72,7 @@ const getAllPostsQuery = } '''; -const getAuthorQuery = r''' +const _getAuthorQuery = r''' query GetAuthor($authorSlug: String!) { user(username: $authorSlug) { id @@ -91,7 +92,7 @@ const getAuthorQuery = r''' } '''; -const getPostsByAuthorQuery = +const _getPostsByAuthorQuery = postFieldsFragment + r''' query GetPostsByAuthorQuery($first: Int!, $after: String, $filter: SearchPostsOfPublicationFilter!) { @@ -109,7 +110,7 @@ const getPostsByAuthorQuery = } '''; -const getPostsByTagQuery = +const _getPostsByTagQuery = postFieldsFragment + r''' query GetPostsByTagQuery($publicationId: ObjectId!, $first: Int!, $after: String, $filter: PublicationPostConnectionFilter!) { @@ -130,7 +131,7 @@ const getPostsByTagQuery = } '''; -const getPostQuery = +const _getPostQuery = postFieldsFragment + r''' query GetPost($id: ID!) { @@ -140,23 +141,22 @@ const getPostQuery = } '''; -class NewsApiService { - static final NewsApiService _instance = NewsApiService._internal(); - factory NewsApiService() => _instance; - NewsApiService._internal(); +@riverpod +NewsApiRepository newsApiRepository(Ref ref) => NewsApiRepository(); - final GraphQLClient client = GraphQLClient( - link: HttpLink(apiUrl), +class NewsApiRepository { + final GraphQLClient _client = GraphQLClient( + link: HttpLink(_apiUrl), cache: GraphQLCache(), ); - final String publicationId = dotenv.get('HASHNODE_PUBLICATION_ID'); + final String _publicationId = dotenv.get('HASHNODE_PUBLICATION_ID'); - GetAllPostsT getAllPosts({String afterCursor = ''}) async { - final result = await client.query( + Future getAllPosts({String afterCursor = ''}) async { + final result = await _client.query( QueryOptions( - document: gql(getAllPostsQuery), + document: gql(_getAllPostsQuery), variables: { - 'publicationId': publicationId, + 'publicationId': _publicationId, 'first': postsPerPage, 'after': afterCursor, }, @@ -167,33 +167,30 @@ class NewsApiService { throw Exception(result.exception.toString()); } - final List posts = result.data!['publication']['posts']['edges']; - final String endCursor = - result.data!['publication']['posts']['pageInfo']['endCursor']; - final bool hasNextPage = - result.data!['publication']['posts']['pageInfo']['hasNextPage']; - - return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + final data = result.data!['publication']['posts']; + return ( + items: (data['edges'] as List).cast>(), + endCursor: data['pageInfo']['endCursor'] as String, + hasNextPage: data['pageInfo']['hasNextPage'] as bool, + ); } Future> getPost(String postId) async { - final result = await client.query( - QueryOptions(document: gql(getPostQuery), variables: {'id': postId}), + final result = await _client.query( + QueryOptions(document: gql(_getPostQuery), variables: {'id': postId}), ); if (result.hasException) { throw Exception(result.exception.toString()); } - final Map post = result.data!['post']; - - return post; + return result.data!['post'] as Map; } Future> getAuthor(String authorSlug) async { - final result = await client.query( + final result = await _client.query( QueryOptions( - document: gql(getAuthorQuery), + document: gql(_getAuthorQuery), variables: {'authorSlug': authorSlug}, ), ); @@ -202,23 +199,21 @@ class NewsApiService { throw Exception(result.exception.toString()); } - final Map author = result.data!['user']; - - return author; + return result.data!['user'] as Map; } - GetAllPostsT getPostsByAuthor( + Future getPostsByAuthor( String authorId, { String afterCursor = '', }) async { - final result = await client.query( + final result = await _client.query( QueryOptions( - document: gql(getPostsByAuthorQuery), + document: gql(_getPostsByAuthorQuery), variables: { 'first': postsPerPage, 'after': afterCursor, 'filter': { - 'publicationId': publicationId, + 'publicationId': _publicationId, 'authorIds': [authorId], }, }, @@ -229,22 +224,23 @@ class NewsApiService { throw Exception(result.exception.toString()); } - final List posts = - result.data!['searchPostsOfPublication']['edges']; - final String endCursor = - result.data!['searchPostsOfPublication']['pageInfo']['endCursor']; - final bool hasNextPage = - result.data!['searchPostsOfPublication']['pageInfo']['hasNextPage']; - - return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + final data = result.data!['searchPostsOfPublication']; + return ( + items: (data['edges'] as List).cast>(), + endCursor: data['pageInfo']['endCursor'] as String, + hasNextPage: data['pageInfo']['hasNextPage'] as bool, + ); } - GetAllPostsT getPostsByTag(String tagSlug, {String afterCursor = ''}) async { - final result = await client.query( + Future getPostsByTag( + String tagSlug, { + String afterCursor = '', + }) async { + final result = await _client.query( QueryOptions( - document: gql(getPostsByTagQuery), + document: gql(_getPostsByTagQuery), variables: { - 'publicationId': publicationId, + 'publicationId': _publicationId, 'first': postsPerPage, 'after': afterCursor, 'filter': { @@ -258,12 +254,11 @@ class NewsApiService { throw Exception(result.exception.toString()); } - final List posts = result.data!['publication']['posts']['edges']; - final String endCursor = - result.data!['publication']['posts']['pageInfo']['endCursor']; - final bool hasNextPage = - result.data!['publication']['posts']['pageInfo']['hasNextPage']; - - return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + final data = result.data!['publication']['posts']; + return ( + items: (data['edges'] as List).cast>(), + endCursor: data['pageInfo']['endCursor'] as String, + hasNextPage: data['pageInfo']['hasNextPage'] as bool, + ); } } diff --git a/mobile-app-new/lib/repositories/news_api_repository.g.dart b/mobile-app-new/lib/repositories/news_api_repository.g.dart new file mode 100644 index 000000000..4ead1c4cd --- /dev/null +++ b/mobile-app-new/lib/repositories/news_api_repository.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_api_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsApiRepository) +final newsApiRepositoryProvider = NewsApiRepositoryProvider._(); + +final class NewsApiRepositoryProvider + extends + $FunctionalProvider< + NewsApiRepository, + NewsApiRepository, + NewsApiRepository + > + with $Provider { + NewsApiRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsApiRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsApiRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + NewsApiRepository create(Ref ref) { + return newsApiRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(NewsApiRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$newsApiRepositoryHash() => r'c2ab6b0169b271263c24f15aee897d85018bbeda'; diff --git a/mobile-app-new/lib/services/news/api_service.dart b/mobile-app-new/lib/services/news/api_service.dart index 7fe436711..f2e2c35bf 100644 --- a/mobile-app-new/lib/services/news/api_service.dart +++ b/mobile-app-new/lib/services/news/api_service.dart @@ -1,269 +1,64 @@ -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:graphql/client.dart'; - -typedef ApiData = ({String endCursor, bool hasNextPage, List posts}); -typedef GetAllPostsT = Future; - -const apiUrl = 'https://gql-beta.hashnode.com'; - -const postsPerPage = 20; - -const postFieldsFragment = r''' - fragment PostFields on Post { - id - slug - title - url - author { - id - username - name - bio { - text - } - profilePicture - socialMediaLinks { - website - twitter - facebook - } - location - } - tags { - id - name - slug - } - coverImage { - url - } - brief - readTimeInMinutes - content { - html - } - seo { - description - } - publishedAt - updatedAt - } - '''; - -const getAllPostsQuery = - postFieldsFragment + - r''' - query GetAllPosts($publicationId: ObjectId!, $first: Int!, $after: String) { - publication(id: $publicationId) { - id - posts(first: $first, after: $after) { - edges { - node { - ...PostFields - } - } - pageInfo { - endCursor - hasNextPage - } - } - } - } - '''; - -const getAuthorQuery = r''' - query GetAuthor($authorSlug: String!) { - user(username: $authorSlug) { - id - username - name - bio { - text - } - profilePicture - socialMediaLinks { - website - twitter - facebook - } - location - } - } - '''; - -const getPostsByAuthorQuery = - postFieldsFragment + - r''' - query GetPostsByAuthorQuery($first: Int!, $after: String, $filter: SearchPostsOfPublicationFilter!) { - searchPostsOfPublication(first: $first, after: $after, filter: $filter) { - edges { - node { - ...PostFields - } - } - pageInfo { - endCursor - hasNextPage - } - } - } - '''; - -const getPostsByTagQuery = - postFieldsFragment + - r''' - query GetPostsByTagQuery($publicationId: ObjectId!, $first: Int!, $after: String, $filter: PublicationPostConnectionFilter!) { - publication(id: $publicationId) { - id - posts(first: $first, after: $after, filter: $filter) { - edges { - node { - ...PostFields - } - } - pageInfo { - endCursor - hasNextPage - } - } - } - } - '''; - -const getPostQuery = - postFieldsFragment + - r''' - query GetPost($id: ID!) { - post(id: $id) { - ...PostFields - } - } - '''; - -class NewsApiService { - static final NewsApiService _instance = NewsApiService._internal(); - factory NewsApiService() => _instance; - NewsApiService._internal(); - - final GraphQLClient client = GraphQLClient( - link: HttpLink(apiUrl), - cache: GraphQLCache(), - ); - final String publicationId = dotenv.get('HASHNODE_PUBLICATION_ID'); - - GetAllPostsT getAllPosts({String afterCursor = ''}) async { - final result = await client.query( - QueryOptions( - document: gql(getAllPostsQuery), - variables: { - 'publicationId': publicationId, - 'first': postsPerPage, - 'after': afterCursor, - }, - ), - ); +import 'package:mobile_app_new/models/news/author_model.dart'; +import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/repositories/news_api_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'api_service.g.dart'; + +typedef PostsPage = ({ + String endCursor, + bool hasNextPage, + List posts, +}); + +@riverpod +NewsService newsService(Ref ref) { + final repo = ref.watch(newsApiRepositoryProvider); + return NewsService(repo); +} - if (result.hasException) { - throw Exception(result.exception.toString()); - } +class NewsService { + final NewsApiRepository _repo; - final List posts = result.data!['publication']['posts']['edges']; - final String endCursor = - result.data!['publication']['posts']['pageInfo']['endCursor']; - final bool hasNextPage = - result.data!['publication']['posts']['pageInfo']['hasNextPage']; + NewsService(this._repo); - return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + Future getAllPosts({String afterCursor = ''}) async { + final raw = await _repo.getAllPosts(afterCursor: afterCursor); + final posts = raw.items + .map((edge) => Post.fromJson(edge['node'] as Map)) + .toList(); + return (posts: posts, endCursor: raw.endCursor, hasNextPage: raw.hasNextPage); } - Future> getPost(String postId) async { - final result = await client.query( - QueryOptions(document: gql(getPostQuery), variables: {'id': postId}), - ); - - if (result.hasException) { - throw Exception(result.exception.toString()); - } - - final Map post = result.data!['post']; - - return post; + Future getPost(String postId) async { + final raw = await _repo.getPost(postId); + return Post.fromJson(raw); } - Future> getAuthor(String authorSlug) async { - final result = await client.query( - QueryOptions( - document: gql(getAuthorQuery), - variables: {'authorSlug': authorSlug}, - ), - ); - - if (result.hasException) { - throw Exception(result.exception.toString()); - } - - final Map author = result.data!['user']; - - return author; + Future getAuthor(String authorSlug) async { + final raw = await _repo.getAuthor(authorSlug); + return Author.fromJson(raw); } - GetAllPostsT getPostsByAuthor( + Future getPostsByAuthor( String authorId, { String afterCursor = '', }) async { - final result = await client.query( - QueryOptions( - document: gql(getPostsByAuthorQuery), - variables: { - 'first': postsPerPage, - 'after': afterCursor, - 'filter': { - 'publicationId': publicationId, - 'authorIds': [authorId], - }, - }, - ), - ); - - if (result.hasException) { - throw Exception(result.exception.toString()); - } - - final List posts = - result.data!['searchPostsOfPublication']['edges']; - final String endCursor = - result.data!['searchPostsOfPublication']['pageInfo']['endCursor']; - final bool hasNextPage = - result.data!['searchPostsOfPublication']['pageInfo']['hasNextPage']; - - return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + final raw = await _repo.getPostsByAuthor(authorId, afterCursor: afterCursor); + final posts = raw.items + .map((edge) => Post.fromJson(edge['node'] as Map)) + .toList(); + return (posts: posts, endCursor: raw.endCursor, hasNextPage: raw.hasNextPage); } - GetAllPostsT getPostsByTag(String tagSlug, {String afterCursor = ''}) async { - final result = await client.query( - QueryOptions( - document: gql(getPostsByTagQuery), - variables: { - 'publicationId': publicationId, - 'first': postsPerPage, - 'after': afterCursor, - 'filter': { - 'tagSlugs': [tagSlug], - }, - }, - ), - ); - - if (result.hasException) { - throw Exception(result.exception.toString()); - } - - final List posts = result.data!['publication']['posts']['edges']; - final String endCursor = - result.data!['publication']['posts']['pageInfo']['endCursor']; - final bool hasNextPage = - result.data!['publication']['posts']['pageInfo']['hasNextPage']; - - return (posts: posts, endCursor: endCursor, hasNextPage: hasNextPage); + Future getPostsByTag( + String tagSlug, { + String afterCursor = '', + }) async { + final raw = await _repo.getPostsByTag(tagSlug, afterCursor: afterCursor); + final posts = raw.items + .map((edge) => Post.fromJson(edge['node'] as Map)) + .toList(); + return (posts: posts, endCursor: raw.endCursor, hasNextPage: raw.hasNextPage); } } diff --git a/mobile-app-new/lib/services/news/api_service.g.dart b/mobile-app-new/lib/services/news/api_service.g.dart new file mode 100644 index 000000000..1ab28a12a --- /dev/null +++ b/mobile-app-new/lib/services/news/api_service.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsService) +final newsServiceProvider = NewsServiceProvider._(); + +final class NewsServiceProvider + extends $FunctionalProvider + with $Provider { + NewsServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsServiceHash(); + + @$internal + @override + $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement(pointer); + + @override + NewsService create(Ref ref) { + return newsService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(NewsService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$newsServiceHash() => r'bcc82311e3ebf3c546d6d05938b92d69e24bc6c9'; From ca23fb9fa6c090384cd6a88dcb3c6aee188184b8 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 2 Aug 2026 19:27:47 +0530 Subject: [PATCH 10/52] feat: go router builder for typed routes --- mobile-app-new/lib/home_page.dart | 89 +++++++++++++++++++ mobile-app-new/lib/main.dart | 88 ------------------ mobile-app-new/lib/routing/learn.dart | 19 ++++ mobile-app-new/lib/routing/learn.g.dart | 42 +++++++++ mobile-app-new/lib/routing/news.dart | 14 ++- mobile-app-new/lib/routing/news.g.dart | 35 ++++++++ mobile-app-new/lib/routing/router.dart | 32 +++++-- mobile-app-new/lib/ui/core/drawer/drawer.dart | 6 +- mobile-app-new/pubspec.lock | 8 ++ mobile-app-new/pubspec.yaml | 1 + 10 files changed, 231 insertions(+), 103 deletions(-) create mode 100644 mobile-app-new/lib/home_page.dart create mode 100644 mobile-app-new/lib/routing/learn.dart create mode 100644 mobile-app-new/lib/routing/learn.g.dart create mode 100644 mobile-app-new/lib/routing/news.g.dart diff --git a/mobile-app-new/lib/home_page.dart b/mobile-app-new/lib/home_page.dart new file mode 100644 index 000000000..83136b250 --- /dev/null +++ b/mobile-app-new/lib/home_page.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // TRY THIS: Try changing the color here to a specific color (to + // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar + // change color while the other colors stay the same. + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + drawer: const DrawerWidget(), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + // + // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" + // action in the IDE, or press "p" in the console), to see the + // wireframe for each widget. + mainAxisAlignment: .center, + children: [ + const Text('You have pushed the button this many times:'), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index 3a80798f8..fe5f6b732 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/routing/router.dart'; import 'package:mobile_app_new/services/dio_service.dart'; -import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; void main() async { await dotenv.load(); @@ -27,90 +26,3 @@ class MyApp extends StatelessWidget { ); } } - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - drawer: const DrawerWidget(), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } -} diff --git a/mobile-app-new/lib/routing/learn.dart b/mobile-app-new/lib/routing/learn.dart new file mode 100644 index 000000000..ab52fc8e8 --- /dev/null +++ b/mobile-app-new/lib/routing/learn.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/home_page.dart'; + +part 'learn.g.dart'; + +const learnLandingPath = '/learn'; + +@TypedGoRoute(path: learnLandingPath) +class LearnLandingRoute extends GoRouteData with $LearnLandingRoute { + const LearnLandingRoute({this.title = 'Hello there'}); + + final String title; + + @override + Widget build(BuildContext context, GoRouterState state) { + return MyHomePage(title: title); + } +} diff --git a/mobile-app-new/lib/routing/learn.g.dart b/mobile-app-new/lib/routing/learn.g.dart new file mode 100644 index 000000000..06d6ba291 --- /dev/null +++ b/mobile-app-new/lib/routing/learn.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'learn.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [$learnLandingRoute]; + +RouteBase get $learnLandingRoute => GoRouteData.$route( + path: '/learn', + hasOverriddenOnExit: false, + factory: $LearnLandingRoute._fromState, +); + +mixin $LearnLandingRoute on GoRouteData { + static LearnLandingRoute _fromState(GoRouterState state) => LearnLandingRoute( + title: state.uri.queryParameters['title'] ?? 'Hello there', + ); + + LearnLandingRoute get _self => this as LearnLandingRoute; + + @override + String get location => GoRouteData.$location( + '/learn', + queryParams: {if (_self.title != 'Hello there') 'title': _self.title}, + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index f97a258cb..78c590ed4 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -1,8 +1,16 @@ +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; +part 'news.g.dart'; + const newsFeedPath = '/news'; -final newsRoutes = [ - GoRoute(path: newsFeedPath, builder: (context, state) => NewsFeedView()), -]; +@TypedGoRoute(path: newsFeedPath) +class NewsFeedRoute extends GoRouteData with $NewsFeedRoute { + const NewsFeedRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const NewsFeedView(); +} diff --git a/mobile-app-new/lib/routing/news.g.dart b/mobile-app-new/lib/routing/news.g.dart new file mode 100644 index 000000000..6ccd55a7a --- /dev/null +++ b/mobile-app-new/lib/routing/news.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [$newsFeedRoute]; + +RouteBase get $newsFeedRoute => GoRouteData.$route( + path: '/news', + hasOverriddenOnExit: false, + factory: $NewsFeedRoute._fromState, +); + +mixin $NewsFeedRoute on GoRouteData { + static NewsFeedRoute _fromState(GoRouterState state) => const NewsFeedRoute(); + + @override + String get location => GoRouteData.$location('/news'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/mobile-app-new/lib/routing/router.dart b/mobile-app-new/lib/routing/router.dart index 8e6f9a45c..ed6513da4 100644 --- a/mobile-app-new/lib/routing/router.dart +++ b/mobile-app-new/lib/routing/router.dart @@ -1,14 +1,28 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:mobile_app_new/main.dart'; -import 'package:mobile_app_new/routing/news.dart'; +import 'package:mobile_app_new/routing/learn.dart' as learn_routes; +import 'package:mobile_app_new/routing/news.dart' as news_routes; + +final _initialLocation = const learn_routes.LearnLandingRoute().location; final router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (context, state) => - const MyHomePage(title: 'Flutter Demo Home Page'), + debugLogDiagnostics: kDebugMode, + initialLocation: _initialLocation, + routes: [...learn_routes.$appRoutes, ...news_routes.$appRoutes], + errorBuilder: (context, state) => Scaffold( + appBar: AppBar(title: const Text('Page Not Found')), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SelectableText(state.error?.toString() ?? 'page not found'), + TextButton( + onPressed: () => context.go(_initialLocation), + child: const Text('Go Home'), + ), + ], + ), ), - ...newsRoutes, - ], + ), ); diff --git a/mobile-app-new/lib/ui/core/drawer/drawer.dart b/mobile-app-new/lib/ui/core/drawer/drawer.dart index 56b97fa34..4ca6a9845 100644 --- a/mobile-app-new/lib/ui/core/drawer/drawer.dart +++ b/mobile-app-new/lib/ui/core/drawer/drawer.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/routing/learn.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/ui/core/drawer/drawer_tile.dart'; @@ -16,12 +16,12 @@ class DrawerWidget extends StatelessWidget { DrawerTile( component: 'Home', icon: Icons.home, - route: () => context.go('/'), + route: () => LearnLandingRoute().go(context), ), DrawerTile( component: 'Tutorials', icon: Icons.forum_outlined, - route: () => context.go(newsFeedPath), + route: () => NewsFeedRoute().go(context), ), ], ), diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index a72ada970..a7ccd675f 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -304,6 +304,14 @@ packages: url: "https://pub.dev" source: hosted version: "17.3.0" + go_router_builder: + dependency: "direct dev" + description: + name: go_router_builder + sha256: "53253ae5bd3d3bfc38c5488ca844f6b054c4b905683667db4544e7ab995fddcc" + url: "https://pub.dev" + source: hosted + version: "4.4.0" gql: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index daa4b0c7d..9d09bde5c 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -58,6 +58,7 @@ dev_dependencies: build_runner: ^2.15.1 freezed: ^3.2.6-dev.1 json_serializable: ^6.14.0 + go_router_builder: ^4.4.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 1c0e91bcbd4342a479b8094117a296e9facbc07d Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 23 Aug 2026 13:29:53 +0400 Subject: [PATCH 11/52] feat: news feed view --- mobile-app-new/analysis_options.yaml | 2 +- .../assets/images/freecodecamp-banner.png | Bin 0 -> 43511 bytes .../assets/images/placeholder-profile-img.png | Bin 0 -> 28277 bytes mobile-app-new/lib/routing/news.dart | 40 ++- mobile-app-new/lib/routing/news.g.dart | 72 ++++- .../lib/services/news/api_service.dart | 8 +- .../lib/services/news/api_service.g.dart | 28 +- mobile-app-new/lib/ui/core/drawer/drawer.dart | 2 +- .../views/news/news-feed/news_feed_view.dart | 105 ++++++- .../news/news-feed/news_feed_viewmodel.dart | 52 ++++ .../news/news-feed/news_feed_viewmodel.g.dart | 54 ++++ .../news_view_handler_view.dart | 53 ++++ .../lib/ui/views/news/widgets/post_tile.dart | 147 +++++++++ mobile-app-new/pubspec.lock | 286 ++++++++++++++++-- mobile-app-new/pubspec.yaml | 9 +- 15 files changed, 800 insertions(+), 58 deletions(-) create mode 100644 mobile-app-new/assets/images/freecodecamp-banner.png create mode 100644 mobile-app-new/assets/images/placeholder-profile-img.png create mode 100644 mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/widgets/post_tile.dart diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml index ade444ad8..34217f3ba 100644 --- a/mobile-app-new/analysis_options.yaml +++ b/mobile-app-new/analysis_options.yaml @@ -33,4 +33,4 @@ analyzer: strict-casts: true plugins: - riverpod_lint: 3.1.4 + riverpod_lint: 3.1.8 diff --git a/mobile-app-new/assets/images/freecodecamp-banner.png b/mobile-app-new/assets/images/freecodecamp-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..1883ea1d05414b3814ab231c1b4736cb1ed69b3c GIT binary patch literal 43511 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�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 literal 0 HcmV?d00001 diff --git a/mobile-app-new/assets/images/placeholder-profile-img.png b/mobile-app-new/assets/images/placeholder-profile-img.png new file mode 100644 index 0000000000000000000000000000000000000000..c45384cabbef1b70e9a8cbb081d8ea8d1f257ee4 GIT binary patch literal 28277 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 IQsxo=2UchWWdHyG literal 0 HcmV?d00001 diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index 78c590ed4..2ab3f10a6 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -1,16 +1,50 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/news-view-handler/news_view_handler_view.dart'; part 'news.g.dart'; +const newsBookmarksPath = '/news/bookmarks'; const newsFeedPath = '/news'; +const newsSearchPath = '/news/search'; + +@TypedShellRoute( + routes: [ + TypedGoRoute(path: newsBookmarksPath), + TypedGoRoute(path: newsFeedPath), + TypedGoRoute(path: newsSearchPath), + ], +) +class NewsShellRoute extends ShellRouteData { + const NewsShellRoute(); + + @override + Widget builder(BuildContext context, GoRouterState state, Widget navigator) { + return NewsViewHandlerView(child: navigator); + } +} + +class NewsBookmarksRoute extends GoRouteData with $NewsBookmarksRoute { + const NewsBookmarksRoute(); + + @override + Page buildPage(BuildContext context, GoRouterState state) => + const NoTransitionPage(child: Center(child: Text('Bookmarks - Coming Soon'))); +} -@TypedGoRoute(path: newsFeedPath) class NewsFeedRoute extends GoRouteData with $NewsFeedRoute { const NewsFeedRoute(); @override - Widget build(BuildContext context, GoRouterState state) => - const NewsFeedView(); + Page buildPage(BuildContext context, GoRouterState state) => + const NoTransitionPage(child: NewsFeedView()); +} + +class NewsSearchRoute extends GoRouteData with $NewsSearchRoute { + const NewsSearchRoute(); + + @override + Page buildPage(BuildContext context, GoRouterState state) => + const NoTransitionPage(child: Center(child: Text('Search - Coming Soon'))); } diff --git a/mobile-app-new/lib/routing/news.g.dart b/mobile-app-new/lib/routing/news.g.dart index 6ccd55a7a..337f9b993 100644 --- a/mobile-app-new/lib/routing/news.g.dart +++ b/mobile-app-new/lib/routing/news.g.dart @@ -6,14 +6,55 @@ part of 'news.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [$newsFeedRoute]; +List get $appRoutes => [$newsShellRoute]; -RouteBase get $newsFeedRoute => GoRouteData.$route( - path: '/news', - hasOverriddenOnExit: false, - factory: $NewsFeedRoute._fromState, +RouteBase get $newsShellRoute => ShellRouteData.$route( + factory: $NewsShellRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: '/news/bookmarks', + hasOverriddenOnExit: false, + factory: $NewsBookmarksRoute._fromState, + ), + GoRouteData.$route( + path: '/news', + hasOverriddenOnExit: false, + factory: $NewsFeedRoute._fromState, + ), + GoRouteData.$route( + path: '/news/search', + hasOverriddenOnExit: false, + factory: $NewsSearchRoute._fromState, + ), + ], ); +extension $NewsShellRouteExtension on NewsShellRoute { + static NewsShellRoute _fromState(GoRouterState state) => + const NewsShellRoute(); +} + +mixin $NewsBookmarksRoute on GoRouteData { + static NewsBookmarksRoute _fromState(GoRouterState state) => + const NewsBookmarksRoute(); + + @override + String get location => GoRouteData.$location('/news/bookmarks'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + mixin $NewsFeedRoute on GoRouteData { static NewsFeedRoute _fromState(GoRouterState state) => const NewsFeedRoute(); @@ -33,3 +74,24 @@ mixin $NewsFeedRoute on GoRouteData { @override void replace(BuildContext context) => context.replace(location); } + +mixin $NewsSearchRoute on GoRouteData { + static NewsSearchRoute _fromState(GoRouterState state) => + const NewsSearchRoute(); + + @override + String get location => GoRouteData.$location('/news/search'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/mobile-app-new/lib/services/news/api_service.dart b/mobile-app-new/lib/services/news/api_service.dart index f2e2c35bf..9abd3d275 100644 --- a/mobile-app-new/lib/services/news/api_service.dart +++ b/mobile-app-new/lib/services/news/api_service.dart @@ -12,15 +12,15 @@ typedef PostsPage = ({ }); @riverpod -NewsService newsService(Ref ref) { +NewsApiService newsApiService(Ref ref) { final repo = ref.watch(newsApiRepositoryProvider); - return NewsService(repo); + return NewsApiService(repo); } -class NewsService { +class NewsApiService { final NewsApiRepository _repo; - NewsService(this._repo); + NewsApiService(this._repo); Future getAllPosts({String afterCursor = ''}) async { final raw = await _repo.getAllPosts(afterCursor: afterCursor); diff --git a/mobile-app-new/lib/services/news/api_service.g.dart b/mobile-app-new/lib/services/news/api_service.g.dart index 1ab28a12a..e2f513d06 100644 --- a/mobile-app-new/lib/services/news/api_service.g.dart +++ b/mobile-app-new/lib/services/news/api_service.g.dart @@ -9,43 +9,43 @@ part of 'api_service.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint, type=warning -@ProviderFor(newsService) -final newsServiceProvider = NewsServiceProvider._(); +@ProviderFor(newsApiService) +final newsApiServiceProvider = NewsApiServiceProvider._(); -final class NewsServiceProvider - extends $FunctionalProvider - with $Provider { - NewsServiceProvider._() +final class NewsApiServiceProvider + extends $FunctionalProvider + with $Provider { + NewsApiServiceProvider._() : super( from: null, argument: null, retry: null, - name: r'newsServiceProvider', + name: r'newsApiServiceProvider', isAutoDispose: true, dependencies: null, $allTransitiveDependencies: null, ); @override - String debugGetCreateSourceHash() => _$newsServiceHash(); + String debugGetCreateSourceHash() => _$newsApiServiceHash(); @$internal @override - $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement $createElement($ProviderPointer pointer) => $ProviderElement(pointer); @override - NewsService create(Ref ref) { - return newsService(ref); + NewsApiService create(Ref ref) { + return newsApiService(ref); } /// {@macro riverpod.override_with_value} - Override overrideWithValue(NewsService value) { + Override overrideWithValue(NewsApiService value) { return $ProviderOverride( origin: this, - providerOverride: $SyncValueProvider(value), + providerOverride: $SyncValueProvider(value), ); } } -String _$newsServiceHash() => r'bcc82311e3ebf3c546d6d05938b92d69e24bc6c9'; +String _$newsApiServiceHash() => r'551c870921b060fc2dd8d4f2ca644c0ad229615f'; diff --git a/mobile-app-new/lib/ui/core/drawer/drawer.dart b/mobile-app-new/lib/ui/core/drawer/drawer.dart index 4ca6a9845..2e4bf484b 100644 --- a/mobile-app-new/lib/ui/core/drawer/drawer.dart +++ b/mobile-app-new/lib/ui/core/drawer/drawer.dart @@ -21,7 +21,7 @@ class DrawerWidget extends StatelessWidget { DrawerTile( component: 'Tutorials', icon: Icons.forum_outlined, - route: () => NewsFeedRoute().go(context), + route: () => const NewsFeedRoute().go(context), ), ], ), diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart index dea118254..3252d1ca8 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart @@ -1,20 +1,109 @@ +import 'dart:developer'; + import 'package:flutter/material.dart'; -import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; -class NewsFeedView extends StatefulWidget { +class NewsFeedView extends ConsumerStatefulWidget { const NewsFeedView({super.key}); @override - State createState() => _NewsFeedViewState(); + ConsumerState createState() => _NewsFeedViewState(); +} + +class _NewsFeedViewState extends ConsumerState { + final _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 1000) { + ref.read(newsFeedProvider.notifier).fetchNextPage(); + } + } + + @override + Widget build(BuildContext context) { + final postsAsync = ref.watch(newsFeedProvider); + + return ColoredBox( + color: FccColors.gray90, + child: postsAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => _ErrorView( + error: error, + onRetry: () => ref.invalidate(newsFeedProvider), + ), + data: (posts) => RefreshIndicator( + onRefresh: () async => ref.invalidate(newsFeedProvider), + backgroundColor: FccColors.gray90, + color: Colors.white, + child: ListView.separated( + controller: _scrollController, + itemCount: posts.length + 1, + separatorBuilder: (_, _) => + const Divider(color: FccColors.gray80, thickness: 1, height: 1), + itemBuilder: (context, index) { + if (index == posts.length) { + return _buildLoadingIndicator(); + } + return PostTile( + key: ValueKey(posts[index].id), + post: posts[index], + ); + }, + ), + ), + ), + ); + } + + Widget _buildLoadingIndicator() { + final notifier = ref.read(newsFeedProvider.notifier); + if (!notifier.hasNextPage) { + log('No more pages to load'); + return const SizedBox.shrink(); + } + + return const Padding( + padding: EdgeInsets.symmetric(vertical: 24), + child: Center(child: CircularProgressIndicator()), + ); + } } -class _NewsFeedViewState extends State { +class _ErrorView extends StatelessWidget { + const _ErrorView({required this.error, required this.onRetry}); + + final Object error; + final VoidCallback onRetry; + @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: Text('Tutorials')), - drawer: const DrawerWidget(), - body: Center(child: Text('LOADING...')), + log('Error loading news feed: $error'); + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Unable to load tutorials.', textAlign: TextAlign.center), + const SizedBox(height: 12), + TextButton(onPressed: onRetry, child: const Text('Retry')), + ], + ), ); } } diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart new file mode 100644 index 000000000..89940187d --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart @@ -0,0 +1,52 @@ +import 'package:flutter/foundation.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/services/news/api_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'news_feed_viewmodel.g.dart'; + +@riverpod +class NewsFeedNotifier extends _$NewsFeedNotifier { + bool _isFetchingNext = false; + + String _endCursor = ''; + bool _hasNextPage = true; + final List _posts = []; + + bool get hasNextPage => _hasNextPage; + + @override + FutureOr> build() async { + return _fetchPage(); + } + + Future> _fetchPage() async { + final service = ref.read(newsApiServiceProvider); + final page = await service.getAllPosts(afterCursor: _endCursor); + + _endCursor = page.endCursor; + _hasNextPage = page.hasNextPage; + _posts.addAll(page.posts); + + return List.unmodifiable(_posts); + } + + Future fetchNextPage() async { + if (!_hasNextPage || _isFetchingNext) return; + + _isFetchingNext = true; + state = await AsyncValue.guard(() => _fetchPage()); + _isFetchingNext = false; + } + + static String parseDate(String date) { + try { + final jiffyDate = Jiffy.parseFromDateTime(DateTime.parse(date)); + return Jiffy.parseFromJiffy(jiffyDate).fromNow().toUpperCase(); + } catch (e) { + debugPrint('Error parsing date: $e'); + return ''; + } + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart new file mode 100644 index 000000000..232eb54ff --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_feed_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(NewsFeedNotifier) +final newsFeedProvider = NewsFeedNotifierProvider._(); + +final class NewsFeedNotifierProvider + extends $AsyncNotifierProvider> { + NewsFeedNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsFeedProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsFeedNotifierHash(); + + @$internal + @override + NewsFeedNotifier create() => NewsFeedNotifier(); +} + +String _$newsFeedNotifierHash() => r'67e151add1a79b2b221aa59bae7185a965b3ee10'; + +abstract class _$NewsFeedNotifier extends $AsyncNotifier> { + FutureOr> build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref>, List>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier>, List>, + AsyncValue>, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart b/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart new file mode 100644 index 000000000..9f4bb2c13 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/routing/news.dart'; +import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; + +class NewsViewHandlerView extends StatelessWidget { + const NewsViewHandlerView({super.key, required this.child}); + + final Widget child; + + static const _titles = ['Bookmarks', 'Tutorials', 'Search']; + static const _paths = [newsBookmarksPath, newsFeedPath, newsSearchPath]; + + int _currentIndex(BuildContext context) { + final location = GoRouterState.of(context).uri.path; + for (int i = 0; i < _paths.length; i++) { + if (location.startsWith(_paths[i])) return i; + } + return 1; + } + + @override + Widget build(BuildContext context) { + final index = _currentIndex(context); + + return Scaffold( + appBar: AppBar(title: Text(_titles[index])), + drawer: const DrawerWidget(), + body: child, + bottomNavigationBar: BottomNavigationBar( + currentIndex: index, + onTap: (i) => context.go(_paths[i]), + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.bookmark_outline_sharp), + label: 'Bookmarks', + tooltip: 'Bookmarks', + ), + BottomNavigationBarItem( + icon: Icon(Icons.article_sharp), + label: 'Tutorials', + tooltip: 'Tutorials', + ), + BottomNavigationBarItem( + icon: Icon(Icons.search_sharp), + label: 'Search', + tooltip: 'Search', + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart new file mode 100644 index 000000000..9b454eac1 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart @@ -0,0 +1,147 @@ +import 'dart:developer'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_viewmodel.dart'; + +class PostTile extends StatelessWidget { + const PostTile({super.key, required this.post}); + + final Post post; + + @override + Widget build(BuildContext context) { + return InkWell( + splashColor: Colors.transparent, + onTap: () { + // TODO: Navigate to post detail + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildCoverImage(), + const SizedBox(height: 8), + Text( + post.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + height: 1.25, + ), + ), + const SizedBox(height: 8), + _buildAuthorRow(), + ], + ), + ), + ); + } + + Widget _buildCoverImage() { + final imageUrl = post.coverImage?.url; + + return ClipRRect( + borderRadius: BorderRadius.circular(12), + child: AspectRatio( + aspectRatio: 16 / 9, + child: Container( + color: FccColors.gray80, + child: imageUrl == null + ? Image.asset( + 'assets/images/freecodecamp-banner.png', + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + placeholder: (context, url) => + Container(color: FccColors.gray80), + errorWidget: (context, url, error) { + log('Error loading image: $url - $imageUrl - $error'); + return Image.asset( + 'assets/images/freecodecamp-banner.png', + fit: BoxFit.cover, + ); + }, + ), + ), + ), + ); + } + + Widget _buildAuthorRow() { + final author = post.author; + final profileUrl = author.profilePicture; + + return Row( + children: [ + Flexible( + child: GestureDetector( + onTap: () { + // TODO: Navigate to author profile + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: SizedBox( + width: 24, + height: 24, + child: profileUrl == null + ? Image.asset( + 'assets/images/placeholder-profile-img.png', + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: profileUrl, + fit: BoxFit.cover, + placeholder: (context, url) => + Container(color: FccColors.gray80), + errorWidget: (context, url, error) => Image.asset( + 'assets/images/placeholder-profile-img.png', + fit: BoxFit.cover, + ), + ), + ), + ), + const SizedBox(width: 8), + Flexible( + child: Text( + author.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13, + color: Colors.white.withValues(alpha: 0.8), + ), + ), + ), + ], + ), + ), + ), + Text( + ' • ', + style: TextStyle( + fontSize: 13, + color: Colors.white.withValues(alpha: 0.5), + ), + ), + Text( + NewsFeedNotifier.parseDate(post.publishedAt), + style: TextStyle( + fontSize: 13, + color: Colors.white.withValues(alpha: 0.5), + ), + ), + ], + ); + } +} diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index a7ccd675f..4f43c4d36 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -61,18 +61,18 @@ packages: dependency: transitive description: name: build_config - sha256: f2c223156a26eea323e6244b85141d76413a80aeee9fe0b380773789fabaf8ae + sha256: "94eaf6708fe64408c632ef2689ca3777b112f9421306ccf4f8c84d7c5c9f83f8" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 + sha256: "79e05eaf15a48d7230b053a4363b8eaac0cc234bbd0134c3229455481f55cbc6" url: "https://pub.dev" source: hosted - version: "4.1.2" + version: "4.1.5" build_runner: dependency: "direct dev" description: @@ -93,10 +93,34 @@ packages: dependency: transitive description: name: built_value - sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" url: "https://pub.dev" source: hosted - version: "8.12.6" + version: "8.12.7" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -129,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" code_builder: dependency: transitive description: @@ -189,18 +221,18 @@ packages: dependency: "direct main" description: name: dio - sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 + sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c" url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.11.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 + sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" fake_async: dependency: transitive description: @@ -209,6 +241,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" file: dependency: transitive description: @@ -230,6 +270,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "1de7849213b4c73c85aca7e0ac687a9a5d82ccdb594366b9dcc26cb6a2189cd2" + url: "https://pub.dev" + source: hosted + version: "3.4.2" flutter_dotenv: dependency: "direct main" description: @@ -300,10 +348,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "5922b2861e2235a3504896f0d6fa07d84141b480cf52eecd2f42cd25585a9e8a" + sha256: d7a3576cb312649eaa51f2356450aed686085fb58fcdebda5b359aa951eef7ea url: "https://pub.dev" source: hosted - version: "17.3.0" + version: "17.5.0" go_router_builder: dependency: "direct dev" description: @@ -392,6 +440,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.19.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" http: dependency: transitive description: @@ -416,6 +472,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + intl: + dependency: transitive + description: + name: intl + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" + url: "https://pub.dev" + source: hosted + version: "0.20.3" io: dependency: transitive description: @@ -432,6 +496,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + jiffy: + dependency: "direct main" + description: + name: jiffy + sha256: cabced5ddea4612f5e0c498df065ee8a1bd810f7ed757db910e95d2aba80161d + url: "https://pub.dev" + source: hosted + version: "6.4.5" + jni: + dependency: transitive + description: + name: jni + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" json_annotation: dependency: "direct main" description: @@ -444,10 +540,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ffcd10cde35a93b2abbbcc26bd9971f4ca93763e8abe78d855e3c4177797e501 + sha256: e45aefa0324f08c683caafbb94b72837aa6193c61822799c916e45f4a263113d url: "https://pub.dev" source: hosted - version: "6.14.0" + version: "6.14.1" leak_tracker: dependency: transitive description: @@ -544,6 +640,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e + url: "https://pub.dev" + source: hosted + version: "9.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_config: dependency: transitive description: @@ -560,6 +672,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" pool: dependency: transitive description: @@ -592,6 +768,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" riverpod: dependency: transitive description: @@ -673,18 +865,18 @@ packages: dependency: transitive description: name: source_gen - sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + sha256: a603f1fb984a7391ae5978d1b92bfaaa08b350dca5c825256f925818f7943bf5 url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "4.2.4" source_helper: dependency: transitive description: name: source_helper - sha256: "4227d54ceefd0bb8ca4c8fcb96e1719dc53f1ee1b6e2ca9d7a6069da160e4eae" + sha256: "5e6f216fdf6376c9f3852381ae037499797a3385377d388b011dac98d303c67c" url: "https://pub.dev" source: hosted - version: "1.3.12" + version: "1.3.13" source_map_stack_trace: dependency: transitive description: @@ -709,6 +901,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b + url: "https://pub.dev" + source: hosted + version: "2.4.3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590" + url: "https://pub.dev" + source: hosted + version: "2.5.11" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f + url: "https://pub.dev" + source: hosted + version: "2.4.3+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50 + url: "https://pub.dev" + source: hosted + version: "2.4.1" stack_trace: dependency: transitive description: @@ -749,6 +981,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "3a7b5d17422dd0f8d5c6c14feaa5a1c65638b9455f871a96f08437562c046931" + url: "https://pub.dev" + source: hosted + version: "3.4.1+2" term_glyph: dependency: transitive description: @@ -853,6 +1093,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" yaml: dependency: transitive description: @@ -863,4 +1111,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.12.2 <4.0.0" - flutter: ">=3.38.0" + flutter: ">=3.44.0" diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 9d09bde5c..9956b4594 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -36,13 +36,15 @@ dependencies: cupertino_icons: ^1.0.8 flutter_riverpod: ^3.3.2 riverpod_annotation: ^4.0.3 - dio: ^5.10.0 - go_router: ^17.3.0 + dio: ^5.11.0 + go_router: ^17.5.0 pretty_dio_logger: ^1.4.0 flutter_dotenv: ^6.0.1 graphql: ^5.2.4 freezed_annotation: ^3.1.0 json_annotation: ^4.12.0 + cached_network_image: ^3.4.1 + jiffy: ^6.3.2 dev_dependencies: flutter_test: @@ -57,7 +59,7 @@ dev_dependencies: riverpod_generator: ^4.0.4 build_runner: ^2.15.1 freezed: ^3.2.6-dev.1 - json_serializable: ^6.14.0 + json_serializable: ^6.14.1 go_router_builder: ^4.4.0 # For information on the generic Dart part of this file, see the @@ -74,6 +76,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - .env + - assets/images/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg From 9d33d133617847907dda18d06ed46065deb070b9 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 23 Aug 2026 13:54:59 +0400 Subject: [PATCH 12/52] feat: post feed tags and fix flickering issue --- .../lib/ui/views/news/widgets/post_tile.dart | 18 ++++++ .../lib/ui/views/news/widgets/tag_button.dart | 64 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 mobile-app-new/lib/ui/views/news/widgets/tag_button.dart diff --git a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart index 9b454eac1..49cc2f0a2 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; class PostTile extends StatelessWidget { const PostTile({super.key, required this.post}); @@ -25,6 +26,23 @@ class PostTile extends StatelessWidget { children: [ _buildCoverImage(), const SizedBox(height: 8), + if (post.tags.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Wrap( + spacing: 0, + runSpacing: 4, + children: [ + for (int i = 0; i < post.tags.length && i < 3; i++) + TagButton( + tagName: post.tags[i].name, + tagSlug: post.tags[i].slug, + compact: true, + key: UniqueKey(), + ), + ], + ), + ), Text( post.title, maxLines: 2, diff --git a/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart b/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart new file mode 100644 index 000000000..404803126 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; + +class TagButton extends StatelessWidget { + const TagButton({ + super.key, + required this.tagName, + required this.tagSlug, + this.compact = false, + }); + + final String tagName; + final String tagSlug; + final bool compact; + + static const _colors = [ + FccColors.purple10, + FccColors.yellow50, + FccColors.blue30, + FccColors.green40, + ]; + + Color get _tagColor => _colors[tagSlug.hashCode.abs() % _colors.length]; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.fromLTRB(0, compact ? 0 : 8, compact ? 6 : 8, 0), + child: InkWell( + onTap: () { + // TODO: Navigate to tag-filtered feed + }, + child: Container( + constraints: BoxConstraints( + maxWidth: + MediaQuery.of(context).size.width * (compact ? 0.35 : 0.45), + ), + decoration: ShapeDecoration( + color: _tagColor, + shape: const StadiumBorder(), + ), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: compact ? 2 : 4, + horizontal: compact ? 6 : 8, + ), + child: Tooltip( + message: '#$tagName', + child: Text( + '#$tagName', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: compact ? 11 : 16, + color: Colors.black, + ), + ), + ), + ), + ), + ), + ); + } +} From a57d88d40e52811d61007685c8913547948dd805 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 23 Aug 2026 17:49:36 +0400 Subject: [PATCH 13/52] feat: basic news post view --- .../lib/repositories/news_api_repository.dart | 22 +- mobile-app-new/lib/routing/news.dart | 26 ++ mobile-app-new/lib/routing/news.g.dart | 67 +++- .../lib/services/news/api_service.dart | 4 +- .../ui/core/html_handler/html_handler.dart | 293 +++++++++++++++++ .../views/news/news-post/news_post_view.dart | 308 ++++++++++++++++++ .../news/news-post/news_post_viewmodel.dart | 14 + .../news/news-post/news_post_viewmodel.g.dart | 99 ++++++ .../views/news/widgets/news_image_view.dart | 43 +++ .../lib/ui/views/news/widgets/post_tile.dart | 3 +- mobile-app-new/pubspec.lock | 250 +++++++++++++- mobile-app-new/pubspec.yaml | 8 + 12 files changed, 1125 insertions(+), 12 deletions(-) create mode 100644 mobile-app-new/lib/ui/core/html_handler/html_handler.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart create mode 100644 mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart diff --git a/mobile-app-new/lib/repositories/news_api_repository.dart b/mobile-app-new/lib/repositories/news_api_repository.dart index a0b060755..f0782dea2 100644 --- a/mobile-app-new/lib/repositories/news_api_repository.dart +++ b/mobile-app-new/lib/repositories/news_api_repository.dart @@ -131,12 +131,14 @@ const _getPostsByTagQuery = } '''; -const _getPostQuery = +const _getPostBySlugQuery = postFieldsFragment + r''' - query GetPost($id: ID!) { - post(id: $id) { - ...PostFields + query GetPostBySlug($publicationId: ObjectId!, $slug: String!) { + publication(id: $publicationId) { + post(slug: $slug) { + ...PostFields + } } } '''; @@ -175,16 +177,22 @@ class NewsApiRepository { ); } - Future> getPost(String postId) async { + Future> getPostBySlug(String slug) async { final result = await _client.query( - QueryOptions(document: gql(_getPostQuery), variables: {'id': postId}), + QueryOptions( + document: gql(_getPostBySlugQuery), + variables: { + 'publicationId': _publicationId, + 'slug': slug, + }, + ), ); if (result.hasException) { throw Exception(result.exception.toString()); } - return result.data!['post'] as Map; + return result.data!['publication']['post'] as Map; } Future> getAuthor(String authorSlug) async { diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index 2ab3f10a6..dabbd1bde 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -1,13 +1,17 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; import 'package:mobile_app_new/ui/views/news/news-view-handler/news_view_handler_view.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/news_image_view.dart'; part 'news.g.dart'; const newsBookmarksPath = '/news/bookmarks'; const newsFeedPath = '/news'; const newsSearchPath = '/news/search'; +const newsPostPath = '/news/:slug'; +const newsImagePath = '/news/image'; @TypedShellRoute( routes: [ @@ -48,3 +52,25 @@ class NewsSearchRoute extends GoRouteData with $NewsSearchRoute { Page buildPage(BuildContext context, GoRouterState state) => const NoTransitionPage(child: Center(child: Text('Search - Coming Soon'))); } + +@TypedGoRoute(path: newsPostPath) +class NewsPostRoute extends GoRouteData with $NewsPostRoute { + const NewsPostRoute({required this.slug}); + + final String slug; + + @override + Widget build(BuildContext context, GoRouterState state) => + NewsPostView(slug: slug); +} + +@TypedGoRoute(path: newsImagePath) +class NewsImageRoute extends GoRouteData with $NewsImageRoute { + const NewsImageRoute({required this.$extra}); + + final String $extra; + + @override + Widget build(BuildContext context, GoRouterState state) => + NewsImageView(imgUrl: $extra); +} diff --git a/mobile-app-new/lib/routing/news.g.dart b/mobile-app-new/lib/routing/news.g.dart index 337f9b993..23df8e85b 100644 --- a/mobile-app-new/lib/routing/news.g.dart +++ b/mobile-app-new/lib/routing/news.g.dart @@ -6,7 +6,11 @@ part of 'news.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [$newsShellRoute]; +List get $appRoutes => [ + $newsShellRoute, + $newsPostRoute, + $newsImageRoute, +]; RouteBase get $newsShellRoute => ShellRouteData.$route( factory: $NewsShellRouteExtension._fromState, @@ -95,3 +99,64 @@ mixin $NewsSearchRoute on GoRouteData { @override void replace(BuildContext context) => context.replace(location); } + +RouteBase get $newsPostRoute => GoRouteData.$route( + path: '/news/:slug', + hasOverriddenOnExit: false, + factory: $NewsPostRoute._fromState, +); + +mixin $NewsPostRoute on GoRouteData { + static NewsPostRoute _fromState(GoRouterState state) => + NewsPostRoute(slug: state.pathParameters['slug']!); + + NewsPostRoute get _self => this as NewsPostRoute; + + @override + String get location => + GoRouteData.$location('/news/${Uri.encodeComponent(_self.slug)}'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $newsImageRoute => GoRouteData.$route( + path: '/news/image', + hasOverriddenOnExit: false, + factory: $NewsImageRoute._fromState, +); + +mixin $NewsImageRoute on GoRouteData { + static NewsImageRoute _fromState(GoRouterState state) => + NewsImageRoute($extra: state.extra as String); + + NewsImageRoute get _self => this as NewsImageRoute; + + @override + String get location => GoRouteData.$location('/news/image'); + + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + + @override + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: _self.$extra); + + @override + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); +} diff --git a/mobile-app-new/lib/services/news/api_service.dart b/mobile-app-new/lib/services/news/api_service.dart index 9abd3d275..c8280e3ff 100644 --- a/mobile-app-new/lib/services/news/api_service.dart +++ b/mobile-app-new/lib/services/news/api_service.dart @@ -30,8 +30,8 @@ class NewsApiService { return (posts: posts, endCursor: raw.endCursor, hasNextPage: raw.hasNextPage); } - Future getPost(String postId) async { - final raw = await _repo.getPost(postId); + Future getPostBySlug(String slug) async { + final raw = await _repo.getPostBySlug(slug); return Post.fromJson(raw); } diff --git a/mobile-app-new/lib/ui/core/html_handler/html_handler.dart b/mobile-app-new/lib/ui/core/html_handler/html_handler.dart new file mode 100644 index 000000000..eacbf3cf9 --- /dev/null +++ b/mobile-app-new/lib/ui/core/html_handler/html_handler.dart @@ -0,0 +1,293 @@ +import 'dart:convert'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_html_table/flutter_html_table.dart'; +import 'package:html/dom.dart' as dom; +import 'package:html/parser.dart' as parser; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/routing/news.dart'; +import 'package:phone_ide/phone_ide.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:youtube_player_iframe/youtube_player_iframe.dart'; + +final Map defaultStyles = { + 'h1': Style( + margin: Margins.only(left: 2, top: 32, right: 2), + fontSize: FontSize.xxLarge, + ), + 'h2': Style( + margin: Margins.only(left: 2, top: 32, right: 2), + fontSize: FontSize.xxLarge, + ), + 'h3': Style( + margin: Margins.only(left: 2, top: 32, right: 2), + fontSize: FontSize.xLarge, + ), + 'h4': Style( + margin: Margins.only(left: 2, top: 32, right: 2), + fontSize: FontSize.large, + ), + 'h5': Style( + margin: Margins.only(left: 2, top: 32, right: 2), + fontSize: FontSize.large, + ), + 'h6': Style( + margin: Margins.only(left: 2, top: 32, right: 2), + fontSize: FontSize.large, + ), + '*:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6)': Style( + fontSize: FontSize.xLarge, + color: Colors.white.withValues(alpha: 0.87), + ), + 'body': Style( + fontFamily: 'Lato', + padding: HtmlPaddings.only(left: 4, right: 4), + ), + 'strong': Style(fontWeight: FontWeight.bold), + 'a': Style( + color: Colors.white, + textDecoration: TextDecoration.underline, + textDecorationColor: Colors.white, + ), + 'p': Style(fontSize: FontSize(20), margin: Margins.only(top: 8, bottom: 8)), + 'li': Style(margin: Margins.only(top: 8), lineHeight: const LineHeight(1.5)), + 'td': Style( + border: const Border(bottom: BorderSide(color: Colors.grey)), + padding: HtmlPaddings.all(12), + backgroundColor: Colors.white, + color: Colors.black, + fontSize: FontSize.medium, + ), + 'th': Style( + padding: HtmlPaddings.all(12), + backgroundColor: FccColors.gray10, + color: Colors.black, + ), + 'th strong': Style(color: Colors.black, fontSize: FontSize.medium), + 'figure': Style(margin: Margins.zero, textAlign: TextAlign.center), + 'figcaption': Style(fontSize: FontSize.medium), + 'code': Style( + fontFamily: 'Hack', + backgroundColor: FccColors.gray75, + color: Colors.white.withValues(alpha: 0.87), + fontSize: FontSize.xLarge, + ), + 'pre': Style(fontFamily: 'Hack'), + // NOTE: Text within backticks in en-US, es, and zh-CN challenge files is + // parsed into spans with this class. + 'span.highlighted-text': Style( + backgroundColor: FccSemanticColors.backgroundTertiary, + ), +}; + +class HTMLParser { + const HTMLParser({Key? key, required this.context}); + + final BuildContext context; + + List parse( + String html, { + bool isSelectable = true, + Map customStyles = const {}, + }) { + dom.Document result = parser.parse(html); + + List elements = []; + + for (int i = 0; i < result.body!.children.length; i++) { + elements.add( + _parseHTMLWidget( + result.body!.children[i].outerHtml, + isSelectable, + customStyles, + ), + ); + } + + return elements; + } + + Widget _parseHTMLWidget( + String child, [ + bool isSelectable = true, + Map customStyles = const {}, + ]) { + Html htmlWidget = Html( + shrinkWrap: true, + data: child, + style: mergeMaps( + defaultStyles, + customStyles, + value: (s0, s1) { + return s0.merge(s1); + }, + ), + onLinkTap: (url, attributes, element) { + launchUrl(Uri.parse(url!.trim())); + }, + extensions: [ + TagWrapExtension( + tagsToWrap: {'table'}, + builder: (child) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: child, + ); + }, + ), + const TableHtmlExtension(), + TagExtension( + tagsToExtend: {'pre'}, + builder: (child) { + var codeElement = child.element!.children.isNotEmpty + ? child + .element! + .children + .first // code element + : child.element!; // pre element + String? currentClass; + + bool codeLanguageIsPresent(List classNames) { + RegExp regExp = RegExp(r'\blang(uage)?\b'); + + for (String className in classNames) { + if (className.contains(regExp)) { + currentClass = className; + return true; + } + } + + return false; + } + + List classes = codeElement.classes.toList(); + + return Editor( + options: EditorOptions( + fontFamily: 'Hack', + takeFullHeight: false, + isEditable: false, + showLinebar: false, + ), + defaultLanguage: codeLanguageIsPresent(classes) + ? currentClass!.split('-')[1] + : '', + defaultValue: codeElement.text.trimRight(), + path: 'example', + ); + }, + ), + TagExtension( + tagsToExtend: {'iframe'}, + builder: (child) { + var isVideo = RegExp('youtube', caseSensitive: false); + var videoUrl = child.attributes['src']; + if (isVideo.hasMatch(videoUrl ?? '')) { + var videoId = videoUrl?.split('/').last.split('?').first; + + YoutubePlayerController controller = + YoutubePlayerController.fromVideoId( + videoId: videoId!, + autoPlay: false, + params: const YoutubePlayerParams( + showControls: true, + showFullscreenButton: true, + strictRelatedVideos: true, + origin: 'https://www.youtube-nocookie.com', + ), + ); + + return YoutubePlayer( + enableFullScreenOnVerticalDrag: false, + controller: controller, + ); + } + + return Container(); + }, + ), + TagExtension( + tagsToExtend: {'code'}, + builder: (child) { + String? parsed = parser.parseFragment(child.innerHtml).text ?? ''; + + return InkWell( + onTap: () { + Clipboard.setData(ClipboardData(text: parsed)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Copied to clipboard: $parsed', + style: const TextStyle(fontSize: 20), + ), + duration: const Duration(seconds: 1), + ), + ); + }, + child: Text( + parsed, + style: const TextStyle( + fontFamily: 'Hack', + color: Colors.white, + fontSize: 18, + backgroundColor: FccColors.gray75, + ), + ), + ); + }, + ), + TagExtension( + tagsToExtend: {'img'}, + builder: (child) { + var imgUrl = child.attributes['src'] ?? ''; + var isDataUrl = Uri.parse(imgUrl).scheme == 'data'; + return Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () => NewsImageRoute($extra: imgUrl).push(context), + child: isDataUrl + ? Image.memory(base64Decode(imgUrl.split(',').last)) + : CachedNetworkImage(imageUrl: imgUrl), + ), + ); + }, + ), + TagExtension( + tagsToExtend: {'blockquote'}, + builder: (node) { + HTMLParser innerParser = HTMLParser(context: context); + List blockQuoteWidgets = innerParser.parse(node.innerHtml); + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: FccColors.blue30, + width: 2, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column(children: [...blockQuoteWidgets]), + ), + ), + ); + }, + ), + ], + ); + + if (isSelectable) { + return SelectionArea(child: htmlWidget); + } else { + return htmlWidget; + } + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart new file mode 100644 index 000000000..73ad26a15 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart @@ -0,0 +1,308 @@ +import 'dart:developer'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; +import 'package:mobile_app_new/ui/views/news/news-post/news_post_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; + +class NewsPostHeader extends StatelessWidget { + const NewsPostHeader({super.key, required this.post}); + + final Post post; + + @override + Widget build(BuildContext context) { + final imageUrl = post.coverImage?.url; + + return Column( + children: [ + AspectRatio( + aspectRatio: 16 / 9, + child: imageUrl == null + ? Image.asset( + 'assets/images/freecodecamp-banner.png', + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + placeholder: (context, url) => + Container(color: FccColors.gray80), + errorWidget: (context, url, error) => Image.asset( + 'assets/images/freecodecamp-banner.png', + fit: BoxFit.cover, + ), + ), + ), + Row( + children: [ + Expanded( + child: Container( + color: FccColors.gray80, + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + post.title, + style: const TextStyle(fontSize: 24, height: 1.5), + ), + Text( + 'Written by ${post.author.name}', + style: const TextStyle(height: 1.5), + ), + if (post.tags.isNotEmpty) + Wrap( + children: [ + for (int j = 0; j < post.tags.length && j < 3; j++) + TagButton( + tagName: post.tags[j].name, + tagSlug: post.tags[j].slug, + ), + ], + ), + ], + ), + ), + ), + ], + ), + ], + ); + } +} + +class NewsPostView extends ConsumerStatefulWidget { + const NewsPostView({super.key, required this.slug}); + + final String slug; + + @override + ConsumerState createState() => _NewsPostViewState(); +} + +class _NewsPostViewState extends ConsumerState { + final ScrollController _scrollController = ScrollController(); + final ScrollController _bottomButtonController = ScrollController(); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_handleBottomButtonAnimation); + } + + @override + void dispose() { + _scrollController.dispose(); + _bottomButtonController.dispose(); + super.dispose(); + } + + double _lastScrollOffset = 0; + + void _handleBottomButtonAnimation() { + if (!_bottomButtonController.hasClients) return; + + if (_scrollController.offset <= _lastScrollOffset) { + // Scrolling up — show buttons + _bottomButtonController.animateTo( + _bottomButtonController.position.maxScrollExtent - 50, + duration: const Duration(milliseconds: 1000), + curve: Curves.easeInOut, + ); + } else { + // Scrolling down — hide buttons + _bottomButtonController.animateTo( + 0, + duration: const Duration(milliseconds: 1000), + curve: Curves.easeInOut, + ); + } + _lastScrollOffset = _scrollController.offset; + } + + void _initBottomButtonAnimation() { + Future.delayed(const Duration(seconds: 1), () { + if (_bottomButtonController.hasClients) { + _bottomButtonController.animateTo( + _bottomButtonController.position.maxScrollExtent - 50, + duration: const Duration(milliseconds: 1000), + curve: Curves.easeInOut, + ); + } + }); + } + + @override + Widget build(BuildContext context) { + final postAsync = ref.watch(newsPostProvider(widget.slug)); + + return Scaffold( + backgroundColor: FccColors.gray90, + body: postAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) { + log('Error loading post: $error'); + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Unable to load post.', textAlign: TextAlign.center), + const SizedBox(height: 12), + TextButton( + onPressed: () => + ref.invalidate(newsPostProvider(widget.slug)), + child: const Text('Retry'), + ), + ], + ), + ); + }, + data: (post) { + final htmlWidgets = _buildLazyLoadedHtml(post); + return Column( + children: [ + Expanded( + child: Stack( + children: [ + ListView.builder( + controller: _scrollController, + itemCount: htmlWidgets.length, + physics: const ClampingScrollPhysics(), + itemBuilder: (context, i) { + return Row(children: [Expanded(child: htmlWidgets[i])]); + }, + ), + _buildBottomButtons(post), + ], + ), + ), + ], + ); + }, + ), + ); + } + + List _buildLazyLoadedHtml(Post post) { + HTMLParser htmlParser = HTMLParser(context: context); + + List elements = htmlParser.parse(post.content.html); + + elements.insert( + 0, + Stack( + children: [ + NewsPostHeader(post: post), + AppBar( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + leading: Tooltip( + message: 'Back', + child: InkWell( + onTap: () => context.pop(), + child: Container( + margin: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50), + color: Colors.black.withValues(alpha: 0.5), + ), + child: const Icon(Icons.arrow_back), + ), + ), + ), + ), + ], + ), + ); + + elements.add(const SizedBox(height: 100)); + + _initBottomButtonAnimation(); + + return elements; + } + + Widget _buildBottomButtons(Post post) { + return Align( + alignment: Alignment.bottomCenter, + child: SizedBox( + height: 75, + width: 300, + child: ListView( + physics: const NeverScrollableScrollPhysics(), + controller: _bottomButtonController, + children: [ + Row( + children: [ + Container(height: 150), + // TODO: Bookmark button + _BottomButton( + label: 'Bookmark', + icon: Icons.bookmark_border, + onPressed: () { + // TODO: Implement bookmark functionality + }, + rightSided: false, + ), + const SizedBox( + height: 35, + child: VerticalDivider(color: Colors.white, width: 0), + ), + _BottomButton( + label: 'Share', + icon: Icons.share, + onPressed: () { + // TODO: Implement share functionality + }, + rightSided: true, + ), + ], + ), + ], + ), + ), + ); + } +} + +class _BottomButton extends StatelessWidget { + const _BottomButton({ + required this.label, + required this.onPressed, + required this.icon, + required this.rightSided, + }); + + final VoidCallback onPressed; + final String label; + final bool rightSided; + final IconData icon; + + @override + Widget build(BuildContext context) { + return Expanded( + child: ElevatedButton.icon( + icon: Icon(icon, color: Colors.white), + onPressed: onPressed, + label: Text(label), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(rightSided ? 0 : 10), + topRight: Radius.circular(rightSided ? 10 : 0), + bottomLeft: Radius.circular(rightSided ? 0 : 10), + bottomRight: Radius.circular(rightSided ? 10 : 0), + ), + ), + backgroundColor: FccColors.gray80, + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart new file mode 100644 index 000000000..4849f1d92 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart @@ -0,0 +1,14 @@ +import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/services/news/api_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'news_post_viewmodel.g.dart'; + +@riverpod +class NewsPostNotifier extends _$NewsPostNotifier { + @override + FutureOr build(String slug) async { + final service = ref.read(newsApiServiceProvider); + return service.getPostBySlug(slug); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart new file mode 100644 index 000000000..371fa747f --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart @@ -0,0 +1,99 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_post_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(NewsPostNotifier) +final newsPostProvider = NewsPostNotifierFamily._(); + +final class NewsPostNotifierProvider + extends $AsyncNotifierProvider { + NewsPostNotifierProvider._({ + required NewsPostNotifierFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'newsPostProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsPostNotifierHash(); + + @override + String toString() { + return r'newsPostProvider' + '' + '($argument)'; + } + + @$internal + @override + NewsPostNotifier create() => NewsPostNotifier(); + + @override + bool operator ==(Object other) { + return other is NewsPostNotifierProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$newsPostNotifierHash() => r'4bd3a49efc3dc02afdc6162c81e9c16f333ef143'; + +final class NewsPostNotifierFamily extends $Family + with + $ClassFamilyOverride< + NewsPostNotifier, + AsyncValue, + Post, + FutureOr, + String + > { + NewsPostNotifierFamily._() + : super( + retry: null, + name: r'newsPostProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + NewsPostNotifierProvider call(String slug) => + NewsPostNotifierProvider._(argument: slug, from: this); + + @override + String toString() => r'newsPostProvider'; +} + +abstract class _$NewsPostNotifier extends $AsyncNotifier { + late final _$args = ref.$arg as String; + String get slug => _$args; + + FutureOr build(String slug); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref, Post>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, Post>, + AsyncValue, + Object?, + Object? + >; + return element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart b/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart new file mode 100644 index 000000000..ac0182f40 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:photo_view/photo_view.dart'; + +class NewsImageView extends StatelessWidget { + const NewsImageView({ + super.key, + required this.imgUrl, + }); + + final String imgUrl; + + bool get _isDataUrl => Uri.parse(imgUrl).scheme == 'data'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + leading: Tooltip( + message: 'Back', + child: InkWell( + onTap: () => context.pop(), + child: const Icon(Icons.arrow_back), + ), + ), + ), + body: PhotoView( + backgroundDecoration: const BoxDecoration( + color: FccColors.gray80, + backgroundBlendMode: BlendMode.color, + ), + imageProvider: _isDataUrl + ? MemoryImage(base64Decode(imgUrl.split(',').last)) + : NetworkImage(imgUrl), + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart index 49cc2f0a2..0b58d1cff 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart @@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; @@ -17,7 +18,7 @@ class PostTile extends StatelessWidget { return InkWell( splashColor: Colors.transparent, onTap: () { - // TODO: Navigate to post detail + NewsPostRoute(slug: post.slug).push(context); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 4f43c4d36..00ed61504 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -170,7 +170,7 @@ packages: source: hosted version: "4.11.1" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" @@ -201,6 +201,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -286,6 +294,38 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.1" + flutter_highlight: + dependency: transitive + description: + name: flutter_highlight + sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter_html: + dependency: "direct main" + description: + name: flutter_html + sha256: "38a2fd702ffdf3243fb7441ab58aa1bc7e6922d95a50db76534de8260638558d" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_html_table: + dependency: "direct main" + description: + name: flutter_html_table + sha256: de15300b1f6d8014e1702e7edfdf3411f362c8fb753e89bac4c99215ea94a4d8 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_layout_grid: + dependency: transitive + description: + name: flutter_layout_grid + sha256: "739e568db97af031d528dfd8a80d333df0e5a310a126e087690fa42cd61dfb5f" + url: "https://pub.dev" + source: hosted + version: "2.0.8" flutter_lints: dependency: "direct dev" description: @@ -432,6 +472,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + highlight: + dependency: transitive + description: + name: highlight + sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21" + url: "https://pub.dev" + source: hosted + version: "0.7.0" hive_ce: dependency: transitive description: @@ -448,6 +496,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + html: + dependency: "direct main" + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: transitive description: @@ -576,6 +632,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + list_counter: + dependency: transitive + description: + name: list_counter + sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 + url: "https://pub.dev" + source: hosted + version: "1.0.2" logging: dependency: transitive description: @@ -720,6 +784,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + phone_ide: + dependency: "direct main" + description: + name: phone_ide + sha256: "6d30607fb42f1df40d95be3fa062715c80ca2731a4254dcf1394c114258690b5" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" + url: "https://pub.dev" + source: hosted + version: "0.15.0" platform: dependency: transitive description: @@ -824,6 +904,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" + url: "https://pub.dev" + source: hosted + version: "2.4.27" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" shelf: dependency: transitive description: @@ -1029,6 +1165,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + url: "https://pub.dev" + source: hosted + version: "6.3.32" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" uuid: dependency: transitive description: @@ -1093,6 +1293,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: d53e1ccf5516f25017e3c9d44c39034db352d20fa34fe200674270242c2c5111 + url: "https://pub.dev" + source: hosted + version: "4.14.1" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: b98656fa4461f8cc05c48a778b4d4883e60ec63e1778348f363f9bb9a477745d + url: "https://pub.dev" + source: hosted + version: "4.14.0" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "1221c1b12f5278791042f2ec2841743784cf25c5a644e23d6680e5d718824f04" + url: "https://pub.dev" + source: hosted + version: "2.15.1" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: c879dd64b87c452aa84381b244d5469da57ba7e8cca6884c7b1e0d406372c12d + url: "https://pub.dev" + source: hosted + version: "3.26.0" xdg_directories: dependency: transitive description: @@ -1109,6 +1341,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" + youtube_player_iframe: + dependency: "direct main" + description: + name: youtube_player_iframe + sha256: "8488d3fd3ce10286356a3e40036cc9ab296c19f399a3141acb70dc18a5231dab" + url: "https://pub.dev" + source: hosted + version: "6.0.2" + youtube_player_iframe_web: + dependency: transitive + description: + name: youtube_player_iframe_web + sha256: "405fccd8b7ae467835fc713bc86a3182470106098eec9aad0c178aec5395efa8" + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.12.2 <4.0.0" flutter: ">=3.44.0" diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 9956b4594..43fa74abc 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -45,6 +45,14 @@ dependencies: json_annotation: ^4.12.0 cached_network_image: ^3.4.1 jiffy: ^6.3.2 + flutter_html: 3.0.0 + flutter_html_table: 3.0.0 + url_launcher: 6.3.2 + youtube_player_iframe: 6.0.2 + phone_ide: 2.0.1 + photo_view: 0.15.0 + collection: 1.19.1 + html: 0.15.6 dev_dependencies: flutter_test: From 52f03b2c03aff1a15c7d085c91123070ef984ff3 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Mon, 24 Aug 2026 16:21:25 +0400 Subject: [PATCH 14/52] chore: clean up code and re-organise pt. 1 --- .../news/news-feed/news_feed_viewmodel.dart | 11 -------- .../views/news/news-post/news_post_view.dart | 26 +++++++------------ .../lib/ui/views/news/widgets/post_tile.dart | 9 +++---- mobile-app-new/lib/utils/date_utils.dart | 12 +++++++++ 4 files changed, 26 insertions(+), 32 deletions(-) create mode 100644 mobile-app-new/lib/utils/date_utils.dart diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart index 89940187d..5bbb08938 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart @@ -1,5 +1,3 @@ -import 'package:flutter/foundation.dart'; -import 'package:jiffy/jiffy.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/services/news/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -40,13 +38,4 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { _isFetchingNext = false; } - static String parseDate(String date) { - try { - final jiffyDate = Jiffy.parseFromDateTime(DateTime.parse(date)); - return Jiffy.parseFromJiffy(jiffyDate).fromNow().toUpperCase(); - } catch (e) { - debugPrint('Error parsing date: $e'); - return ''; - } - } } diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart index 73ad26a15..178c2b3aa 100644 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart @@ -32,7 +32,7 @@ class NewsPostHeader extends StatelessWidget { imageUrl: imageUrl, fit: BoxFit.cover, placeholder: (context, url) => - Container(color: FccColors.gray80), + ColoredBox(color: FccColors.gray80), errorWidget: (context, url, error) => Image.asset( 'assets/images/freecodecamp-banner.png', fit: BoxFit.cover, @@ -165,23 +165,17 @@ class _NewsPostViewState extends ConsumerState { }, data: (post) { final htmlWidgets = _buildLazyLoadedHtml(post); - return Column( + return Stack( children: [ - Expanded( - child: Stack( - children: [ - ListView.builder( - controller: _scrollController, - itemCount: htmlWidgets.length, - physics: const ClampingScrollPhysics(), - itemBuilder: (context, i) { - return Row(children: [Expanded(child: htmlWidgets[i])]); - }, - ), - _buildBottomButtons(post), - ], - ), + ListView.builder( + controller: _scrollController, + itemCount: htmlWidgets.length, + physics: const ClampingScrollPhysics(), + itemBuilder: (context, i) { + return Row(children: [Expanded(child: htmlWidgets[i])]); + }, ), + _buildBottomButtons(post), ], ); }, diff --git a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart index 0b58d1cff..904921215 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_viewmodel.dart'; +import 'package:mobile_app_new/utils/date_utils.dart'; import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; class PostTile extends StatelessWidget { @@ -39,7 +39,6 @@ class PostTile extends StatelessWidget { tagName: post.tags[i].name, tagSlug: post.tags[i].slug, compact: true, - key: UniqueKey(), ), ], ), @@ -80,7 +79,7 @@ class PostTile extends StatelessWidget { imageUrl: imageUrl, fit: BoxFit.cover, placeholder: (context, url) => - Container(color: FccColors.gray80), + ColoredBox(color: FccColors.gray80), errorWidget: (context, url, error) { log('Error loading image: $url - $imageUrl - $error'); return Image.asset( @@ -122,7 +121,7 @@ class PostTile extends StatelessWidget { imageUrl: profileUrl, fit: BoxFit.cover, placeholder: (context, url) => - Container(color: FccColors.gray80), + ColoredBox(color: FccColors.gray80), errorWidget: (context, url, error) => Image.asset( 'assets/images/placeholder-profile-img.png', fit: BoxFit.cover, @@ -154,7 +153,7 @@ class PostTile extends StatelessWidget { ), ), Text( - NewsFeedNotifier.parseDate(post.publishedAt), + parseDate(post.publishedAt), style: TextStyle( fontSize: 13, color: Colors.white.withValues(alpha: 0.5), diff --git a/mobile-app-new/lib/utils/date_utils.dart b/mobile-app-new/lib/utils/date_utils.dart new file mode 100644 index 000000000..9ecd4e9b2 --- /dev/null +++ b/mobile-app-new/lib/utils/date_utils.dart @@ -0,0 +1,12 @@ +import 'package:flutter/foundation.dart'; +import 'package:jiffy/jiffy.dart'; + +String parseDate(String date) { + try { + final jiffyDate = Jiffy.parseFromDateTime(DateTime.parse(date)); + return Jiffy.parseFromJiffy(jiffyDate).fromNow().toUpperCase(); + } catch (e) { + debugPrint('Error parsing date: $e'); + return ''; + } +} From 356025c1e6cabc22cbbd8cbd2ffec81d2d0d40b1 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Mon, 24 Aug 2026 21:55:13 +0400 Subject: [PATCH 15/52] chore: clean up code and re-organise pt. 2 --- mobile-app-new/lib/ui/core/html_handler/html_handler.dart | 8 ++------ .../lib/ui/views/news/news-feed/news_feed_view.dart | 4 ---- .../ui/views/news/news-feed/news_feed_viewmodel.g.dart | 2 +- .../lib/ui/views/news/news-post/news_post_view.dart | 5 +++++ 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/mobile-app-new/lib/ui/core/html_handler/html_handler.dart b/mobile-app-new/lib/ui/core/html_handler/html_handler.dart index eacbf3cf9..b8d04c6b4 100644 --- a/mobile-app-new/lib/ui/core/html_handler/html_handler.dart +++ b/mobile-app-new/lib/ui/core/html_handler/html_handler.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html_table/flutter_html_table.dart'; -import 'package:html/dom.dart' as dom; import 'package:html/parser.dart' as parser; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/routing/news.dart'; @@ -94,7 +93,7 @@ class HTMLParser { bool isSelectable = true, Map customStyles = const {}, }) { - dom.Document result = parser.parse(html); + final result = parser.parse(html); List elements = []; @@ -267,10 +266,7 @@ class HTMLParser { child: Container( decoration: const BoxDecoration( border: Border( - left: BorderSide( - color: FccColors.blue30, - width: 2, - ), + left: BorderSide(color: FccColors.blue30, width: 2), ), ), child: Padding( diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart index 3252d1ca8..54eb14d08 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -75,7 +73,6 @@ class _NewsFeedViewState extends ConsumerState { Widget _buildLoadingIndicator() { final notifier = ref.read(newsFeedProvider.notifier); if (!notifier.hasNextPage) { - log('No more pages to load'); return const SizedBox.shrink(); } @@ -94,7 +91,6 @@ class _ErrorView extends StatelessWidget { @override Widget build(BuildContext context) { - log('Error loading news feed: $error'); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart index 232eb54ff..a039feca6 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart @@ -33,7 +33,7 @@ final class NewsFeedNotifierProvider NewsFeedNotifier create() => NewsFeedNotifier(); } -String _$newsFeedNotifierHash() => r'67e151add1a79b2b221aa59bae7185a965b3ee10'; +String _$newsFeedNotifierHash() => r'486e9dc45f3b60e10877a8ae23ba66c77e15a65f'; abstract class _$NewsFeedNotifier extends $AsyncNotifier> { FutureOr> build(); diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart index 178c2b3aa..1728ec22a 100644 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart @@ -90,6 +90,8 @@ class _NewsPostViewState extends ConsumerState { final ScrollController _scrollController = ScrollController(); final ScrollController _bottomButtonController = ScrollController(); + bool _hasInitializedAnimation = false; + @override void initState() { super.initState(); @@ -127,6 +129,9 @@ class _NewsPostViewState extends ConsumerState { } void _initBottomButtonAnimation() { + if (_hasInitializedAnimation) return; + _hasInitializedAnimation = true; + Future.delayed(const Duration(seconds: 1), () { if (_bottomButtonController.hasClients) { _bottomButtonController.animateTo( From 353b03bcbc95e791132bcfe36244e7b7d8461fd0 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Mon, 24 Aug 2026 22:12:13 +0400 Subject: [PATCH 16/52] feat: news post sharing --- .../views/news/news-post/news_post_view.dart | 22 ++++++++-- mobile-app-new/pubspec.lock | 40 +++++++++++++++++++ mobile-app-new/pubspec.yaml | 1 + 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart index 1728ec22a..bbeccfc04 100644 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart @@ -9,6 +9,7 @@ import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; +import 'package:share_plus/share_plus.dart'; class NewsPostHeader extends StatelessWidget { const NewsPostHeader({super.key, required this.post}); @@ -90,6 +91,8 @@ class _NewsPostViewState extends ConsumerState { final ScrollController _scrollController = ScrollController(); final ScrollController _bottomButtonController = ScrollController(); + final GlobalKey _shareButtonKey = GlobalKey(); + bool _hasInitializedAnimation = false; @override @@ -227,6 +230,19 @@ class _NewsPostViewState extends ConsumerState { return elements; } + void _sharePost(Post post) { + final box = _shareButtonKey.currentContext?.findRenderObject() as RenderBox?; + + SharePlus.instance.share( + ShareParams( + text: '${post.title}\n\n${post.url}', + sharePositionOrigin: box == null + ? null + : box.localToGlobal(Offset.zero) & box.size, + ), + ); + } + Widget _buildBottomButtons(Post post) { return Align( alignment: Alignment.bottomCenter, @@ -254,11 +270,10 @@ class _NewsPostViewState extends ConsumerState { child: VerticalDivider(color: Colors.white, width: 0), ), _BottomButton( + key: _shareButtonKey, label: 'Share', icon: Icons.share, - onPressed: () { - // TODO: Implement share functionality - }, + onPressed: () => _sharePost(post), rightSided: true, ), ], @@ -272,6 +287,7 @@ class _NewsPostViewState extends ConsumerState { class _BottomButton extends StatelessWidget { const _BottomButton({ + super.key, required this.label, required this.onPressed, required this.icon, diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 00ed61504..b59c695c2 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -193,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" + url: "https://pub.dev" + source: hosted + version: "0.3.5+4" crypto: dependency: transitive description: @@ -257,6 +265,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + ffi_leak_tracker: + dependency: transitive + description: + name: ffi_leak_tracker + sha256: "4093d4ef9ca06ffe2786e73bfb25e22aa92112b9bb4ec941f11e3e6b61489a97" + url: "https://pub.dev" + source: hosted + version: "0.1.2" file: dependency: transitive description: @@ -904,6 +920,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "34f00f9becd2743c1fb05363d624f9f70d37f7ccdcdda47450bc0b8c9d327b8c" + url: "https://pub.dev" + source: hosted + version: "13.3.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "365ef7379fc22507256adda3385152942ffce08935452bc972c2e52a0bebae41" + url: "https://pub.dev" + source: hosted + version: "7.2.0" shared_preferences: dependency: transitive description: @@ -1325,6 +1357,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.26.0" + win32: + dependency: transitive + description: + name: win32 + sha256: a0b93865d5644f11cf6a8c3f6db909f1ec168958b5805f6cc684adea957cd63d + url: "https://pub.dev" + source: hosted + version: "6.4.0" xdg_directories: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 43fa74abc..12c3d6b8b 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: youtube_player_iframe: 6.0.2 phone_ide: 2.0.1 photo_view: 0.15.0 + share_plus: 13.3.0 collection: 1.19.1 html: 0.15.6 From f77c49075714e019ac24a6bcd906560d67fab86b Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 25 Aug 2026 18:28:46 +0400 Subject: [PATCH 17/52] feat: news search views --- .../lib/constants/radio_articles.dart | 10 + .../lib/models/news/search_post_model.dart | 37 ++ .../news/search_post_model.freezed.dart | 576 ++++++++++++++++++ .../lib/models/news/search_post_model.g.dart | 38 ++ .../repositories/news_search_repository.dart | 33 + .../news_search_repository.g.dart | 58 ++ mobile-app-new/lib/routing/news.dart | 27 +- mobile-app-new/lib/routing/news.g.dart | 56 +- .../lib/services/news/search_service.dart | 34 ++ .../lib/services/news/search_service.g.dart | 57 ++ .../news/news-search/news_search_view.dart | 91 +++ .../news-search/news_search_viewmodel.dart | 34 ++ .../news-search/news_search_viewmodel.g.dart | 70 +++ .../news_view_handler_view.dart | 7 +- .../news/widgets/search_result_tile.dart | 131 ++++ mobile-app-new/pubspec.lock | 72 +++ mobile-app-new/pubspec.yaml | 1 + 17 files changed, 1288 insertions(+), 44 deletions(-) create mode 100644 mobile-app-new/lib/constants/radio_articles.dart create mode 100644 mobile-app-new/lib/models/news/search_post_model.dart create mode 100644 mobile-app-new/lib/models/news/search_post_model.freezed.dart create mode 100644 mobile-app-new/lib/models/news/search_post_model.g.dart create mode 100644 mobile-app-new/lib/repositories/news_search_repository.dart create mode 100644 mobile-app-new/lib/repositories/news_search_repository.g.dart create mode 100644 mobile-app-new/lib/services/news/search_service.dart create mode 100644 mobile-app-new/lib/services/news/search_service.g.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-search/news_search_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.g.dart create mode 100644 mobile-app-new/lib/ui/views/news/widgets/search_result_tile.dart diff --git a/mobile-app-new/lib/constants/radio_articles.dart b/mobile-app-new/lib/constants/radio_articles.dart new file mode 100644 index 000000000..7e1286038 --- /dev/null +++ b/mobile-app-new/lib/constants/radio_articles.dart @@ -0,0 +1,10 @@ +// NOTE: Post IDs for radio-related articles, hidden from search results on iOS. +const radioArticles = { + '66bb9216add24ba427325101', + '66b90438941d2f900bad52b1', + '66b8d2d40c9c1d363b7c4213', + '66b8d5cfd482a18d3e02825a', + '66b8d2d957c651c38343a959', + '66b8d2d7064c610cf26d29ae', + '66b8d46af8e5d39507c4c100', +}; diff --git a/mobile-app-new/lib/models/news/search_post_model.dart b/mobile-app-new/lib/models/news/search_post_model.dart new file mode 100644 index 000000000..3b987ab8c --- /dev/null +++ b/mobile-app-new/lib/models/news/search_post_model.dart @@ -0,0 +1,37 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'search_post_model.freezed.dart'; +part 'search_post_model.g.dart'; + +@freezed +abstract class SearchAuthor with _$SearchAuthor { + const factory SearchAuthor({required String name, String? profileImage}) = + _SearchAuthor; + + factory SearchAuthor.fromJson(Map json) => + _$SearchAuthorFromJson(json); +} + +// Algolia search result post model +@freezed +abstract class SearchPost with _$SearchPost { + const SearchPost._(); + + const factory SearchPost({ + required String objectID, + required String title, + required String url, + required SearchAuthor author, + String? featureImage, + String? publishedAt, + }) = _SearchPost; + + factory SearchPost.fromJson(Map json) => + _$SearchPostFromJson(json); + + // NOTE: Algolia search results do not include the slug, so we derive it from the URL. + String get slug { + final segments = Uri.parse(url).pathSegments.where((s) => s.isNotEmpty); + return segments.isEmpty ? '' : segments.last; + } +} diff --git a/mobile-app-new/lib/models/news/search_post_model.freezed.dart b/mobile-app-new/lib/models/news/search_post_model.freezed.dart new file mode 100644 index 000000000..46a06f108 --- /dev/null +++ b/mobile-app-new/lib/models/news/search_post_model.freezed.dart @@ -0,0 +1,576 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'search_post_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$SearchAuthor { + + String get name; String? get profileImage; +/// Create a copy of SearchAuthor +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SearchAuthorCopyWith get copyWith => _$SearchAuthorCopyWithImpl(this as SearchAuthor, _$identity); + + /// Serializes this SearchAuthor to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SearchAuthor&&(identical(other.name, name) || other.name == name)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,profileImage); + +@override +String toString() { + return 'SearchAuthor(name: $name, profileImage: $profileImage)'; +} + + +} + +/// @nodoc +abstract mixin class $SearchAuthorCopyWith<$Res> { + factory $SearchAuthorCopyWith(SearchAuthor value, $Res Function(SearchAuthor) _then) = _$SearchAuthorCopyWithImpl; +@useResult +$Res call({ + String name, String? profileImage +}); + + + + +} +/// @nodoc +class _$SearchAuthorCopyWithImpl<$Res> + implements $SearchAuthorCopyWith<$Res> { + _$SearchAuthorCopyWithImpl(this._self, this._then); + + final SearchAuthor _self; + final $Res Function(SearchAuthor) _then; + +/// Create a copy of SearchAuthor +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? profileImage = freezed,}) { + return _then(_self.copyWith( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,profileImage: freezed == profileImage ? _self.profileImage : profileImage // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SearchAuthor]. +extension SearchAuthorPatterns on SearchAuthor { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SearchAuthor value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SearchAuthor() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SearchAuthor value) $default,){ +final _that = this; +switch (_that) { +case _SearchAuthor(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SearchAuthor value)? $default,){ +final _that = this; +switch (_that) { +case _SearchAuthor() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String name, String? profileImage)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SearchAuthor() when $default != null: +return $default(_that.name,_that.profileImage);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String name, String? profileImage) $default,) {final _that = this; +switch (_that) { +case _SearchAuthor(): +return $default(_that.name,_that.profileImage);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String name, String? profileImage)? $default,) {final _that = this; +switch (_that) { +case _SearchAuthor() when $default != null: +return $default(_that.name,_that.profileImage);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SearchAuthor implements SearchAuthor { + const _SearchAuthor({required this.name, this.profileImage}); + factory _SearchAuthor.fromJson(Map json) => _$SearchAuthorFromJson(json); + +@override final String name; +@override final String? profileImage; + +/// Create a copy of SearchAuthor +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SearchAuthorCopyWith<_SearchAuthor> get copyWith => __$SearchAuthorCopyWithImpl<_SearchAuthor>(this, _$identity); + +@override +Map toJson() { + return _$SearchAuthorToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SearchAuthor&&(identical(other.name, name) || other.name == name)&&(identical(other.profileImage, profileImage) || other.profileImage == profileImage)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,profileImage); + +@override +String toString() { + return 'SearchAuthor(name: $name, profileImage: $profileImage)'; +} + + +} + +/// @nodoc +abstract mixin class _$SearchAuthorCopyWith<$Res> implements $SearchAuthorCopyWith<$Res> { + factory _$SearchAuthorCopyWith(_SearchAuthor value, $Res Function(_SearchAuthor) _then) = __$SearchAuthorCopyWithImpl; +@override @useResult +$Res call({ + String name, String? profileImage +}); + + + + +} +/// @nodoc +class __$SearchAuthorCopyWithImpl<$Res> + implements _$SearchAuthorCopyWith<$Res> { + __$SearchAuthorCopyWithImpl(this._self, this._then); + + final _SearchAuthor _self; + final $Res Function(_SearchAuthor) _then; + +/// Create a copy of SearchAuthor +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? profileImage = freezed,}) { + return _then(_SearchAuthor( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,profileImage: freezed == profileImage ? _self.profileImage : profileImage // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$SearchPost { + + String get objectID; String get title; String get url; SearchAuthor get author; String? get featureImage; String? get publishedAt; +/// Create a copy of SearchPost +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SearchPostCopyWith get copyWith => _$SearchPostCopyWithImpl(this as SearchPost, _$identity); + + /// Serializes this SearchPost to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SearchPost&&(identical(other.objectID, objectID) || other.objectID == objectID)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&(identical(other.featureImage, featureImage) || other.featureImage == featureImage)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,objectID,title,url,author,featureImage,publishedAt); + +@override +String toString() { + return 'SearchPost(objectID: $objectID, title: $title, url: $url, author: $author, featureImage: $featureImage, publishedAt: $publishedAt)'; +} + + +} + +/// @nodoc +abstract mixin class $SearchPostCopyWith<$Res> { + factory $SearchPostCopyWith(SearchPost value, $Res Function(SearchPost) _then) = _$SearchPostCopyWithImpl; +@useResult +$Res call({ + String objectID, String title, String url, SearchAuthor author, String? featureImage, String? publishedAt +}); + + +$SearchAuthorCopyWith<$Res> get author; + +} +/// @nodoc +class _$SearchPostCopyWithImpl<$Res> + implements $SearchPostCopyWith<$Res> { + _$SearchPostCopyWithImpl(this._self, this._then); + + final SearchPost _self; + final $Res Function(SearchPost) _then; + +/// Create a copy of SearchPost +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? objectID = null,Object? title = null,Object? url = null,Object? author = null,Object? featureImage = freezed,Object? publishedAt = freezed,}) { + return _then(_self.copyWith( +objectID: null == objectID ? _self.objectID : objectID // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as SearchAuthor,featureImage: freezed == featureImage ? _self.featureImage : featureImage // ignore: cast_nullable_to_non_nullable +as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String?, + )); +} +/// Create a copy of SearchPost +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SearchAuthorCopyWith<$Res> get author { + + return $SearchAuthorCopyWith<$Res>(_self.author, (value) { + return _then(_self.copyWith(author: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [SearchPost]. +extension SearchPostPatterns on SearchPost { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SearchPost value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SearchPost() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SearchPost value) $default,){ +final _that = this; +switch (_that) { +case _SearchPost(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SearchPost value)? $default,){ +final _that = this; +switch (_that) { +case _SearchPost() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String objectID, String title, String url, SearchAuthor author, String? featureImage, String? publishedAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SearchPost() when $default != null: +return $default(_that.objectID,_that.title,_that.url,_that.author,_that.featureImage,_that.publishedAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String objectID, String title, String url, SearchAuthor author, String? featureImage, String? publishedAt) $default,) {final _that = this; +switch (_that) { +case _SearchPost(): +return $default(_that.objectID,_that.title,_that.url,_that.author,_that.featureImage,_that.publishedAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String objectID, String title, String url, SearchAuthor author, String? featureImage, String? publishedAt)? $default,) {final _that = this; +switch (_that) { +case _SearchPost() when $default != null: +return $default(_that.objectID,_that.title,_that.url,_that.author,_that.featureImage,_that.publishedAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SearchPost extends SearchPost { + const _SearchPost({required this.objectID, required this.title, required this.url, required this.author, this.featureImage, this.publishedAt}): super._(); + factory _SearchPost.fromJson(Map json) => _$SearchPostFromJson(json); + +@override final String objectID; +@override final String title; +@override final String url; +@override final SearchAuthor author; +@override final String? featureImage; +@override final String? publishedAt; + +/// Create a copy of SearchPost +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SearchPostCopyWith<_SearchPost> get copyWith => __$SearchPostCopyWithImpl<_SearchPost>(this, _$identity); + +@override +Map toJson() { + return _$SearchPostToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SearchPost&&(identical(other.objectID, objectID) || other.objectID == objectID)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&(identical(other.featureImage, featureImage) || other.featureImage == featureImage)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,objectID,title,url,author,featureImage,publishedAt); + +@override +String toString() { + return 'SearchPost(objectID: $objectID, title: $title, url: $url, author: $author, featureImage: $featureImage, publishedAt: $publishedAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$SearchPostCopyWith<$Res> implements $SearchPostCopyWith<$Res> { + factory _$SearchPostCopyWith(_SearchPost value, $Res Function(_SearchPost) _then) = __$SearchPostCopyWithImpl; +@override @useResult +$Res call({ + String objectID, String title, String url, SearchAuthor author, String? featureImage, String? publishedAt +}); + + +@override $SearchAuthorCopyWith<$Res> get author; + +} +/// @nodoc +class __$SearchPostCopyWithImpl<$Res> + implements _$SearchPostCopyWith<$Res> { + __$SearchPostCopyWithImpl(this._self, this._then); + + final _SearchPost _self; + final $Res Function(_SearchPost) _then; + +/// Create a copy of SearchPost +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? objectID = null,Object? title = null,Object? url = null,Object? author = null,Object? featureImage = freezed,Object? publishedAt = freezed,}) { + return _then(_SearchPost( +objectID: null == objectID ? _self.objectID : objectID // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as SearchAuthor,featureImage: freezed == featureImage ? _self.featureImage : featureImage // ignore: cast_nullable_to_non_nullable +as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +/// Create a copy of SearchPost +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SearchAuthorCopyWith<$Res> get author { + + return $SearchAuthorCopyWith<$Res>(_self.author, (value) { + return _then(_self.copyWith(author: value)); + }); +} +} + +// dart format on diff --git a/mobile-app-new/lib/models/news/search_post_model.g.dart b/mobile-app-new/lib/models/news/search_post_model.g.dart new file mode 100644 index 000000000..9b03d6f51 --- /dev/null +++ b/mobile-app-new/lib/models/news/search_post_model.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_post_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_SearchAuthor _$SearchAuthorFromJson(Map json) => + _SearchAuthor( + name: json['name'] as String, + profileImage: json['profileImage'] as String?, + ); + +Map _$SearchAuthorToJson(_SearchAuthor instance) => + { + 'name': instance.name, + 'profileImage': instance.profileImage, + }; + +_SearchPost _$SearchPostFromJson(Map json) => _SearchPost( + objectID: json['objectID'] as String, + title: json['title'] as String, + url: json['url'] as String, + author: SearchAuthor.fromJson(json['author'] as Map), + featureImage: json['featureImage'] as String?, + publishedAt: json['publishedAt'] as String?, +); + +Map _$SearchPostToJson(_SearchPost instance) => + { + 'objectID': instance.objectID, + 'title': instance.title, + 'url': instance.url, + 'author': instance.author, + 'featureImage': instance.featureImage, + 'publishedAt': instance.publishedAt, + }; diff --git a/mobile-app-new/lib/repositories/news_search_repository.dart b/mobile-app-new/lib/repositories/news_search_repository.dart new file mode 100644 index 000000000..5f3235436 --- /dev/null +++ b/mobile-app-new/lib/repositories/news_search_repository.dart @@ -0,0 +1,33 @@ +import 'dart:developer'; + +import 'package:algolia_helper_flutter/algolia_helper_flutter.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'news_search_repository.g.dart'; + +const _indexName = 'news'; + +@riverpod +NewsSearchRepository newsSearchRepository(Ref ref) { + final repository = NewsSearchRepository(); + ref.onDispose(repository.dispose); + return repository; +} + +class NewsSearchRepository { + final HitsSearcher _searcher = HitsSearcher( + applicationID: dotenv.get('ALGOLIAAPPID'), + apiKey: dotenv.get('ALGOLIAKEY'), + indexName: _indexName, + ); + + Stream get responses => _searcher.responses; + + void query(String term) => _searcher.query(term); + + void dispose() { + log('Disposing NewsSearchRepository'); + _searcher.dispose(); + } +} diff --git a/mobile-app-new/lib/repositories/news_search_repository.g.dart b/mobile-app-new/lib/repositories/news_search_repository.g.dart new file mode 100644 index 000000000..1e566c8a0 --- /dev/null +++ b/mobile-app-new/lib/repositories/news_search_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_search_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsSearchRepository) +final newsSearchRepositoryProvider = NewsSearchRepositoryProvider._(); + +final class NewsSearchRepositoryProvider + extends + $FunctionalProvider< + NewsSearchRepository, + NewsSearchRepository, + NewsSearchRepository + > + with $Provider { + NewsSearchRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsSearchRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsSearchRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + NewsSearchRepository create(Ref ref) { + return newsSearchRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(NewsSearchRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$newsSearchRepositoryHash() => + r'e98a8d3f4250ccf351e79082c1d019c6bf48bf27'; diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index dabbd1bde..2b8459e4c 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; +import 'package:mobile_app_new/ui/views/news/news-search/news_search_view.dart'; import 'package:mobile_app_new/ui/views/news/news-view-handler/news_view_handler_view.dart'; import 'package:mobile_app_new/ui/views/news/widgets/news_image_view.dart'; @@ -34,7 +35,9 @@ class NewsBookmarksRoute extends GoRouteData with $NewsBookmarksRoute { @override Page buildPage(BuildContext context, GoRouterState state) => - const NoTransitionPage(child: Center(child: Text('Bookmarks - Coming Soon'))); + const NoTransitionPage( + child: Center(child: Text('Bookmarks - Coming Soon')), + ); } class NewsFeedRoute extends GoRouteData with $NewsFeedRoute { @@ -50,27 +53,27 @@ class NewsSearchRoute extends GoRouteData with $NewsSearchRoute { @override Page buildPage(BuildContext context, GoRouterState state) => - const NoTransitionPage(child: Center(child: Text('Search - Coming Soon'))); + const NoTransitionPage(child: NewsSearchView()); } -@TypedGoRoute(path: newsPostPath) -class NewsPostRoute extends GoRouteData with $NewsPostRoute { - const NewsPostRoute({required this.slug}); +@TypedGoRoute(path: newsImagePath) +class NewsImageRoute extends GoRouteData with $NewsImageRoute { + const NewsImageRoute({required this.$extra}); - final String slug; + final String $extra; @override Widget build(BuildContext context, GoRouterState state) => - NewsPostView(slug: slug); + NewsImageView(imgUrl: $extra); } -@TypedGoRoute(path: newsImagePath) -class NewsImageRoute extends GoRouteData with $NewsImageRoute { - const NewsImageRoute({required this.$extra}); +@TypedGoRoute(path: newsPostPath) +class NewsPostRoute extends GoRouteData with $NewsPostRoute { + const NewsPostRoute({required this.slug}); - final String $extra; + final String slug; @override Widget build(BuildContext context, GoRouterState state) => - NewsImageView(imgUrl: $extra); + NewsPostView(slug: slug); } diff --git a/mobile-app-new/lib/routing/news.g.dart b/mobile-app-new/lib/routing/news.g.dart index 23df8e85b..60c40d3f9 100644 --- a/mobile-app-new/lib/routing/news.g.dart +++ b/mobile-app-new/lib/routing/news.g.dart @@ -8,8 +8,8 @@ part of 'news.dart'; List get $appRoutes => [ $newsShellRoute, - $newsPostRoute, $newsImageRoute, + $newsPostRoute, ]; RouteBase get $newsShellRoute => ShellRouteData.$route( @@ -100,63 +100,63 @@ mixin $NewsSearchRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -RouteBase get $newsPostRoute => GoRouteData.$route( - path: '/news/:slug', +RouteBase get $newsImageRoute => GoRouteData.$route( + path: '/news/image', hasOverriddenOnExit: false, - factory: $NewsPostRoute._fromState, + factory: $NewsImageRoute._fromState, ); -mixin $NewsPostRoute on GoRouteData { - static NewsPostRoute _fromState(GoRouterState state) => - NewsPostRoute(slug: state.pathParameters['slug']!); +mixin $NewsImageRoute on GoRouteData { + static NewsImageRoute _fromState(GoRouterState state) => + NewsImageRoute($extra: state.extra as String); - NewsPostRoute get _self => this as NewsPostRoute; + NewsImageRoute get _self => this as NewsImageRoute; @override - String get location => - GoRouteData.$location('/news/${Uri.encodeComponent(_self.slug)}'); + String get location => GoRouteData.$location('/news/image'); @override - void go(BuildContext context) => context.go(location); + void go(BuildContext context) => context.go(location, extra: _self.$extra); @override - Future push(BuildContext context) => context.push(location); + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); @override void pushReplacement(BuildContext context) => - context.pushReplacement(location); + context.pushReplacement(location, extra: _self.$extra); @override - void replace(BuildContext context) => context.replace(location); + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); } -RouteBase get $newsImageRoute => GoRouteData.$route( - path: '/news/image', +RouteBase get $newsPostRoute => GoRouteData.$route( + path: '/news/:slug', hasOverriddenOnExit: false, - factory: $NewsImageRoute._fromState, + factory: $NewsPostRoute._fromState, ); -mixin $NewsImageRoute on GoRouteData { - static NewsImageRoute _fromState(GoRouterState state) => - NewsImageRoute($extra: state.extra as String); +mixin $NewsPostRoute on GoRouteData { + static NewsPostRoute _fromState(GoRouterState state) => + NewsPostRoute(slug: state.pathParameters['slug']!); - NewsImageRoute get _self => this as NewsImageRoute; + NewsPostRoute get _self => this as NewsPostRoute; @override - String get location => GoRouteData.$location('/news/image'); + String get location => + GoRouteData.$location('/news/${Uri.encodeComponent(_self.slug)}'); @override - void go(BuildContext context) => context.go(location, extra: _self.$extra); + void go(BuildContext context) => context.go(location); @override - Future push(BuildContext context) => - context.push(location, extra: _self.$extra); + Future push(BuildContext context) => context.push(location); @override void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: _self.$extra); + context.pushReplacement(location); @override - void replace(BuildContext context) => - context.replace(location, extra: _self.$extra); + void replace(BuildContext context) => context.replace(location); } diff --git a/mobile-app-new/lib/services/news/search_service.dart b/mobile-app-new/lib/services/news/search_service.dart new file mode 100644 index 000000000..f50c0ab51 --- /dev/null +++ b/mobile-app-new/lib/services/news/search_service.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:algolia_helper_flutter/algolia_helper_flutter.dart'; +import 'package:mobile_app_new/constants/radio_articles.dart'; +import 'package:mobile_app_new/models/news/search_post_model.dart'; +import 'package:mobile_app_new/repositories/news_search_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'search_service.g.dart'; + +@riverpod +NewsSearchService newsSearchService(Ref ref) { + final repo = ref.watch(newsSearchRepositoryProvider); + return NewsSearchService(repo); +} + +class NewsSearchService { + final NewsSearchRepository _repository; + + NewsSearchService(this._repository); + + Stream> get results => _repository.responses.map(_toPosts); + + void query(String term) => _repository.query(term); + + List _toPosts(SearchResponse response) { + // NOTE: Radio articles are hidden on iOS. + final hits = Platform.isIOS + ? response.hits.where((hit) => !radioArticles.contains(hit['objectID'])) + : response.hits; + + return hits.map((hit) => SearchPost.fromJson(hit)).toList(); + } +} diff --git a/mobile-app-new/lib/services/news/search_service.g.dart b/mobile-app-new/lib/services/news/search_service.g.dart new file mode 100644 index 000000000..86c7c22d0 --- /dev/null +++ b/mobile-app-new/lib/services/news/search_service.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsSearchService) +final newsSearchServiceProvider = NewsSearchServiceProvider._(); + +final class NewsSearchServiceProvider + extends + $FunctionalProvider< + NewsSearchService, + NewsSearchService, + NewsSearchService + > + with $Provider { + NewsSearchServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsSearchServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsSearchServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + NewsSearchService create(Ref ref) { + return newsSearchService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(NewsSearchService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$newsSearchServiceHash() => r'7bc8728f9bbde4042a5f3511d86f59729a5e8074'; diff --git a/mobile-app-new/lib/ui/views/news/news-search/news_search_view.dart b/mobile-app-new/lib/ui/views/news/news-search/news_search_view.dart new file mode 100644 index 000000000..da278aadc --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-search/news_search_view.dart @@ -0,0 +1,91 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/models/news/search_post_model.dart'; +import 'package:mobile_app_new/ui/views/news/news-search/news_search_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/search_result_tile.dart'; + +class NewsSearchView extends ConsumerStatefulWidget { + const NewsSearchView({super.key}); + + @override + ConsumerState createState() => _NewsSearchViewState(); +} + +class _NewsSearchViewState extends ConsumerState { + final TextEditingController _searchBarController = TextEditingController(); + + String _searchTerm = ''; + + @override + void dispose() { + _searchBarController.dispose(); + super.dispose(); + } + + void _onSearchTermChanged(String term) { + setState(() => _searchTerm = term); + ref.read(newsSearchProvider.notifier).search(term); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TextField( + controller: _searchBarController, + onChanged: _onSearchTermChanged, + textInputAction: TextInputAction.search, + decoration: const InputDecoration( + hintText: 'SEARCH TUTORIALS...', + fillColor: FccColors.gray80, + filled: true, + border: InputBorder.none, + ), + ), + const Divider(color: FccColors.gray90, thickness: 4, height: 4), + Expanded(child: _buildResults()), + ], + ); + } + + Widget _buildResults() { + if (_searchTerm.isEmpty) { + return const Center(child: Text('Search for tutorials')); + } + + return ref + .watch(newsSearchProvider) + .when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) { + log('Error loading tutorials: $error\n$stackTrace'); + return const Center( + child: Text( + 'There was an error loading tutorials \n please try again', + textAlign: TextAlign.center, + ), + ); + }, + data: (posts) => posts.isEmpty + ? const Center(child: Text('No Tutorials Found')) + : _buildResultsList(posts), + ); + } + + Widget _buildResultsList(List posts) { + return Scrollbar( + thumbVisibility: true, + trackVisibility: true, + child: ListView.separated( + physics: const ClampingScrollPhysics(), + itemCount: posts.length, + separatorBuilder: (context, index) => + Divider(color: FccColors.gray80, thickness: 1, height: 1), + itemBuilder: (context, index) => SearchResultTile(post: posts[index]), + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.dart new file mode 100644 index 000000000..a1ef2716c --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.dart @@ -0,0 +1,34 @@ +import 'package:mobile_app_new/models/news/search_post_model.dart'; +import 'package:mobile_app_new/services/news/search_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'news_search_viewmodel.g.dart'; + +@riverpod +class NewsSearchNotifier extends _$NewsSearchNotifier { + @override + AsyncValue> build() { + final subscription = ref + .watch(newsSearchServiceProvider) + .results + .listen( + (results) => state = AsyncData(results), + onError: (Object error, StackTrace stackTrace) => + state = AsyncError(error, stackTrace), + ); + + ref.onDispose(subscription.cancel); + + return const AsyncData([]); + } + + void search(String term) { + if (term.isEmpty) { + state = const AsyncData([]); + return; + } + + state = const AsyncLoading(); + ref.read(newsSearchServiceProvider).query(term); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.g.dart new file mode 100644 index 000000000..1429d3968 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.g.dart @@ -0,0 +1,70 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_search_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(NewsSearchNotifier) +final newsSearchProvider = NewsSearchNotifierProvider._(); + +final class NewsSearchNotifierProvider + extends + $NotifierProvider>> { + NewsSearchNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsSearchProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsSearchNotifierHash(); + + @$internal + @override + NewsSearchNotifier create() => NewsSearchNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AsyncValue> value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>>(value), + ); + } +} + +String _$newsSearchNotifierHash() => + r'c9774ff6d3b50f833b36425af89b45ee16143109'; + +abstract class _$NewsSearchNotifier + extends $Notifier>> { + AsyncValue> build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = + this.ref + as $Ref>, AsyncValue>>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + AsyncValue>, + AsyncValue> + >, + AsyncValue>, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart b/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart index 9f4bb2c13..0810ec987 100644 --- a/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart @@ -10,13 +10,12 @@ class NewsViewHandlerView extends StatelessWidget { static const _titles = ['Bookmarks', 'Tutorials', 'Search']; static const _paths = [newsBookmarksPath, newsFeedPath, newsSearchPath]; + static const _feedIndex = 1; int _currentIndex(BuildContext context) { final location = GoRouterState.of(context).uri.path; - for (int i = 0; i < _paths.length; i++) { - if (location.startsWith(_paths[i])) return i; - } - return 1; + final index = _paths.indexOf(location); + return index == -1 ? _feedIndex : index; } @override diff --git a/mobile-app-new/lib/ui/views/news/widgets/search_result_tile.dart b/mobile-app-new/lib/ui/views/news/widgets/search_result_tile.dart new file mode 100644 index 000000000..d0299eb40 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/search_result_tile.dart @@ -0,0 +1,131 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/models/news/search_post_model.dart'; +import 'package:mobile_app_new/routing/news.dart'; +import 'package:mobile_app_new/utils/date_utils.dart'; + +class SearchResultTile extends StatelessWidget { + const SearchResultTile({super.key, required this.post}); + + final SearchPost post; + + @override + Widget build(BuildContext context) { + final publishedAt = post.publishedAt; + + return InkWell( + onTap: () => NewsPostRoute(slug: post.slug).push(context), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildCoverImage(), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + post.title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + height: 1.3, + ), + ), + const SizedBox(height: 8), + _buildAuthorRow(), + if (publishedAt != null) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + parseDate(publishedAt), + style: TextStyle( + fontSize: 12, + color: Colors.white.withValues(alpha: 0.5), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildCoverImage() { + final imageUrl = post.featureImage; + + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: SizedBox( + width: 120, + height: 80, + child: imageUrl == null + ? Image.asset( + 'assets/images/freecodecamp-banner.png', + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + placeholder: (context, url) => + ColoredBox(color: FccColors.gray80), + errorWidget: (context, url, error) => Image.asset( + 'assets/images/freecodecamp-banner.png', + fit: BoxFit.cover, + ), + ), + ), + ); + } + + Widget _buildAuthorRow() { + final profileImageUrl = post.author.profileImage; + + return Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: SizedBox( + width: 24, + height: 24, + child: profileImageUrl == null + ? Image.asset( + 'assets/images/placeholder-profile-img.png', + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: profileImageUrl, + fit: BoxFit.cover, + placeholder: (context, url) => + ColoredBox(color: FccColors.gray80), + errorWidget: (context, url, error) => Image.asset( + 'assets/images/placeholder-profile-img.png', + fit: BoxFit.cover, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + post.author.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 13, + color: Colors.white.withValues(alpha: 0.7), + ), + ), + ), + ], + ); + } +} diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index b59c695c2..1427eb30e 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -9,6 +9,54 @@ packages: url: "https://pub.dev" source: hosted version: "99.0.0" + algolia_client_core: + dependency: transitive + description: + name: algolia_client_core + sha256: c254e51e30e9b6609a689c1b2a32b185243edd5efb348e624787ebfac8d54da3 + url: "https://pub.dev" + source: hosted + version: "1.52.0" + algolia_client_insights: + dependency: transitive + description: + name: algolia_client_insights + sha256: bbd670cbe476a7e928e33b233f5fccf134c6fa638ef1984f8a405a48bc3e5b51 + url: "https://pub.dev" + source: hosted + version: "1.52.0" + algolia_client_search: + dependency: transitive + description: + name: algolia_client_search + sha256: "97872b4c487e204b0ee7d58229e82f4f79f4123ec42d21aaa7c0ce590fcf1248" + url: "https://pub.dev" + source: hosted + version: "1.49.2" + algolia_helper_flutter: + dependency: "direct main" + description: + name: algolia_helper_flutter + sha256: "4add0a73f476bbac3204ae1ffbb5e66f73cdaf6ae6496038256d9c56d970a7d9" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + algolia_insights: + dependency: transitive + description: + name: algolia_insights + sha256: "2d94aecfee207e8c0cb403ec9cdcf98baacdfa89054d5f2a26fbca87f358e0b4" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + algoliasearch: + dependency: transitive + description: + name: algoliasearch + sha256: "76e64c5582f30c63f9771c3888d81b3a514222765e3b88ceec3f808e677f8431" + url: "https://pub.dev" + source: hosted + version: "1.49.2" analyzer: dependency: transitive description: @@ -25,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.3" + archive: + dependency: transitive + description: + name: archive + sha256: ace891da0862b0e4cabbb064ee3fd87b2728b898949fdb366d83fe98342c9f19 + url: "https://pub.dev" + source: hosted + version: "4.2.0" args: dependency: transitive description: @@ -496,6 +552,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" hive_ce: dependency: transitive description: @@ -840,6 +904,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" pretty_dio_logger: dependency: "direct main" description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 12c3d6b8b..6a3a8f157 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -52,6 +52,7 @@ dependencies: phone_ide: 2.0.1 photo_view: 0.15.0 share_plus: 13.3.0 + algolia_helper_flutter: 1.6.0 collection: 1.19.1 html: 0.15.6 From cb0ee13c21882e106cecf13e078133d1d2b116c1 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 25 Aug 2026 22:52:00 +0400 Subject: [PATCH 18/52] feat: news tags posts feed --- mobile-app-new/lib/routing/news.dart | 16 +++ mobile-app-new/lib/routing/news.g.dart | 35 ++++++ .../views/news/news-feed/news_feed_view.dart | 101 +---------------- .../news/news-feed/news_feed_viewmodel.g.dart | 54 --------- .../news/news-feed/news_tag_feed_view.dart | 20 ++++ .../views/news/news-feed/post_feed_list.dart | 107 ++++++++++++++++++ ...del.dart => post_feed_list_viewmodel.dart} | 9 +- .../news-feed/post_feed_list_viewmodel.g.dart | 99 ++++++++++++++++ .../lib/ui/views/news/widgets/tag_button.dart | 3 +- 9 files changed, 287 insertions(+), 157 deletions(-) delete mode 100644 mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-feed/news_tag_feed_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-feed/post_feed_list.dart rename mobile-app-new/lib/ui/views/news/news-feed/{news_feed_viewmodel.dart => post_feed_list_viewmodel.dart} (77%) create mode 100644 mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.g.dart diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index 2b8459e4c..6f9846e90 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; import 'package:mobile_app_new/ui/views/news/news-search/news_search_view.dart'; +import 'package:mobile_app_new/ui/views/news/news-feed/news_tag_feed_view.dart'; import 'package:mobile_app_new/ui/views/news/news-view-handler/news_view_handler_view.dart'; import 'package:mobile_app_new/ui/views/news/widgets/news_image_view.dart'; @@ -13,6 +14,7 @@ const newsFeedPath = '/news'; const newsSearchPath = '/news/search'; const newsPostPath = '/news/:slug'; const newsImagePath = '/news/image'; +const newsTagFeedPath = '/news/tag/:tagSlug'; @TypedShellRoute( routes: [ @@ -67,6 +69,20 @@ class NewsImageRoute extends GoRouteData with $NewsImageRoute { NewsImageView(imgUrl: $extra); } +@TypedGoRoute(path: newsTagFeedPath) +class NewsTagFeedRoute extends GoRouteData with $NewsTagFeedRoute { + const NewsTagFeedRoute({required this.tagSlug, this.$extra}); + + final String tagSlug; + + // NOTE: Tag display name + final String? $extra; + + @override + Widget build(BuildContext context, GoRouterState state) => + NewsTagFeedView(tagSlug: tagSlug, tagName: $extra); +} + @TypedGoRoute(path: newsPostPath) class NewsPostRoute extends GoRouteData with $NewsPostRoute { const NewsPostRoute({required this.slug}); diff --git a/mobile-app-new/lib/routing/news.g.dart b/mobile-app-new/lib/routing/news.g.dart index 60c40d3f9..f0bee5c76 100644 --- a/mobile-app-new/lib/routing/news.g.dart +++ b/mobile-app-new/lib/routing/news.g.dart @@ -9,6 +9,7 @@ part of 'news.dart'; List get $appRoutes => [ $newsShellRoute, $newsImageRoute, + $newsTagFeedRoute, $newsPostRoute, ]; @@ -131,6 +132,40 @@ mixin $NewsImageRoute on GoRouteData { context.replace(location, extra: _self.$extra); } +RouteBase get $newsTagFeedRoute => GoRouteData.$route( + path: '/news/tag/:tagSlug', + hasOverriddenOnExit: false, + factory: $NewsTagFeedRoute._fromState, +); + +mixin $NewsTagFeedRoute on GoRouteData { + static NewsTagFeedRoute _fromState(GoRouterState state) => NewsTagFeedRoute( + tagSlug: state.pathParameters['tagSlug']!, + $extra: state.extra as String?, + ); + + NewsTagFeedRoute get _self => this as NewsTagFeedRoute; + + @override + String get location => + GoRouteData.$location('/news/tag/${Uri.encodeComponent(_self.tagSlug)}'); + + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + + @override + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: _self.$extra); + + @override + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); +} + RouteBase get $newsPostRoute => GoRouteData.$route( path: '/news/:slug', hasOverriddenOnExit: false, diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart index 54eb14d08..0fd2c44d0 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart @@ -1,105 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_viewmodel.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; +import 'package:mobile_app_new/ui/views/news/news-feed/post_feed_list.dart'; -class NewsFeedView extends ConsumerStatefulWidget { +class NewsFeedView extends StatelessWidget { const NewsFeedView({super.key}); @override - ConsumerState createState() => _NewsFeedViewState(); -} - -class _NewsFeedViewState extends ConsumerState { - final _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); - _scrollController.addListener(_onScroll); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - void _onScroll() { - if (_scrollController.position.pixels >= - _scrollController.position.maxScrollExtent - 1000) { - ref.read(newsFeedProvider.notifier).fetchNextPage(); - } - } - - @override - Widget build(BuildContext context) { - final postsAsync = ref.watch(newsFeedProvider); - - return ColoredBox( - color: FccColors.gray90, - child: postsAsync.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => _ErrorView( - error: error, - onRetry: () => ref.invalidate(newsFeedProvider), - ), - data: (posts) => RefreshIndicator( - onRefresh: () async => ref.invalidate(newsFeedProvider), - backgroundColor: FccColors.gray90, - color: Colors.white, - child: ListView.separated( - controller: _scrollController, - itemCount: posts.length + 1, - separatorBuilder: (_, _) => - const Divider(color: FccColors.gray80, thickness: 1, height: 1), - itemBuilder: (context, index) { - if (index == posts.length) { - return _buildLoadingIndicator(); - } - return PostTile( - key: ValueKey(posts[index].id), - post: posts[index], - ); - }, - ), - ), - ), - ); - } - - Widget _buildLoadingIndicator() { - final notifier = ref.read(newsFeedProvider.notifier); - if (!notifier.hasNextPage) { - return const SizedBox.shrink(); - } - - return const Padding( - padding: EdgeInsets.symmetric(vertical: 24), - child: Center(child: CircularProgressIndicator()), - ); - } -} - -class _ErrorView extends StatelessWidget { - const _ErrorView({required this.error, required this.onRetry}); - - final Object error; - final VoidCallback onRetry; - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Unable to load tutorials.', textAlign: TextAlign.center), - const SizedBox(height: 12), - TextButton(onPressed: onRetry, child: const Text('Retry')), - ], - ), - ); - } + Widget build(BuildContext context) => const PostFeedList(); } diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart deleted file mode 100644 index a039feca6..000000000 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.g.dart +++ /dev/null @@ -1,54 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'news_feed_viewmodel.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint, type=warning - -@ProviderFor(NewsFeedNotifier) -final newsFeedProvider = NewsFeedNotifierProvider._(); - -final class NewsFeedNotifierProvider - extends $AsyncNotifierProvider> { - NewsFeedNotifierProvider._() - : super( - from: null, - argument: null, - retry: null, - name: r'newsFeedProvider', - isAutoDispose: true, - dependencies: null, - $allTransitiveDependencies: null, - ); - - @override - String debugGetCreateSourceHash() => _$newsFeedNotifierHash(); - - @$internal - @override - NewsFeedNotifier create() => NewsFeedNotifier(); -} - -String _$newsFeedNotifierHash() => r'486e9dc45f3b60e10877a8ae23ba66c77e15a65f'; - -abstract class _$NewsFeedNotifier extends $AsyncNotifier> { - FutureOr> build(); - @$mustCallSuper - @override - WhenComplete runBuild() { - final ref = this.ref as $Ref>, List>; - final element = - ref.element - as $ClassProviderElement< - AnyNotifier>, List>, - AsyncValue>, - Object?, - Object? - >; - return element.handleCreate(ref, build); - } -} diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_tag_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_tag_feed_view.dart new file mode 100644 index 000000000..8049b7ab0 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_tag_feed_view.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/ui/views/news/news-feed/post_feed_list.dart'; + +class NewsTagFeedView extends StatelessWidget { + const NewsTagFeedView({super.key, required this.tagSlug, this.tagName}); + + final String tagSlug; + final String? tagName; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Tutorials about ${tagName ?? tagSlug}')), + backgroundColor: FccColors.gray90, + body: PostFeedList(tagSlug: tagSlug), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list.dart b/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list.dart new file mode 100644 index 000000000..983085b29 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/ui/views/news/news-feed/post_feed_list_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; + +class PostFeedList extends ConsumerStatefulWidget { + const PostFeedList({super.key, this.tagSlug = ''}); + + final String tagSlug; + + @override + ConsumerState createState() => _PostFeedListState(); +} + +class _PostFeedListState extends ConsumerState { + final _scrollController = ScrollController(); + + NewsFeedNotifierProvider get _provider => + newsFeedProvider(tagSlug: widget.tagSlug); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 1000) { + ref.read(_provider.notifier).fetchNextPage(); + } + } + + @override + Widget build(BuildContext context) { + final postsAsync = ref.watch(_provider); + + return ColoredBox( + color: FccColors.gray90, + child: postsAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => + _ErrorView(error: error, onRetry: () => ref.invalidate(_provider)), + data: (posts) => RefreshIndicator( + onRefresh: () async => ref.invalidate(_provider), + backgroundColor: FccColors.gray90, + color: Colors.white, + child: ListView.separated( + controller: _scrollController, + itemCount: posts.length + 1, + separatorBuilder: (_, _) => + const Divider(color: FccColors.gray80, thickness: 1, height: 1), + itemBuilder: (context, index) { + if (index == posts.length) { + return _buildLoadingIndicator(); + } + return PostTile( + key: ValueKey(posts[index].id), + post: posts[index], + ); + }, + ), + ), + ), + ); + } + + Widget _buildLoadingIndicator() { + if (!ref.read(_provider.notifier).hasNextPage) { + return const SizedBox.shrink(); + } + + return const Padding( + padding: EdgeInsets.symmetric(vertical: 24), + child: Center(child: CircularProgressIndicator()), + ); + } +} + +class _ErrorView extends StatelessWidget { + const _ErrorView({required this.error, required this.onRetry}); + + final Object error; + final VoidCallback onRetry; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Unable to load tutorials.', textAlign: TextAlign.center), + const SizedBox(height: 12), + TextButton(onPressed: onRetry, child: const Text('Retry')), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.dart similarity index 77% rename from mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart rename to mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.dart index 5bbb08938..807a9a883 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_viewmodel.dart +++ b/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.dart @@ -2,7 +2,7 @@ import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/services/news/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'news_feed_viewmodel.g.dart'; +part 'post_feed_list_viewmodel.g.dart'; @riverpod class NewsFeedNotifier extends _$NewsFeedNotifier { @@ -15,13 +15,15 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { bool get hasNextPage => _hasNextPage; @override - FutureOr> build() async { + FutureOr> build({String tagSlug = ''}) async { return _fetchPage(); } Future> _fetchPage() async { final service = ref.read(newsApiServiceProvider); - final page = await service.getAllPosts(afterCursor: _endCursor); + final page = tagSlug.isEmpty + ? await service.getAllPosts(afterCursor: _endCursor) + : await service.getPostsByTag(tagSlug, afterCursor: _endCursor); _endCursor = page.endCursor; _hasNextPage = page.hasNextPage; @@ -37,5 +39,4 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { state = await AsyncValue.guard(() => _fetchPage()); _isFetchingNext = false; } - } diff --git a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.g.dart new file mode 100644 index 000000000..b8736ff50 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.g.dart @@ -0,0 +1,99 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'post_feed_list_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(NewsFeedNotifier) +final newsFeedProvider = NewsFeedNotifierFamily._(); + +final class NewsFeedNotifierProvider + extends $AsyncNotifierProvider> { + NewsFeedNotifierProvider._({ + required NewsFeedNotifierFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'newsFeedProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsFeedNotifierHash(); + + @override + String toString() { + return r'newsFeedProvider' + '' + '($argument)'; + } + + @$internal + @override + NewsFeedNotifier create() => NewsFeedNotifier(); + + @override + bool operator ==(Object other) { + return other is NewsFeedNotifierProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$newsFeedNotifierHash() => r'8a9c5876f06d573366cae016d2d97e3aba37584b'; + +final class NewsFeedNotifierFamily extends $Family + with + $ClassFamilyOverride< + NewsFeedNotifier, + AsyncValue>, + List, + FutureOr>, + String + > { + NewsFeedNotifierFamily._() + : super( + retry: null, + name: r'newsFeedProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + NewsFeedNotifierProvider call({String tagSlug = ''}) => + NewsFeedNotifierProvider._(argument: tagSlug, from: this); + + @override + String toString() => r'newsFeedProvider'; +} + +abstract class _$NewsFeedNotifier extends $AsyncNotifier> { + late final _$args = ref.$arg as String; + String get tagSlug => _$args; + + FutureOr> build({String tagSlug = ''}); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref>, List>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier>, List>, + AsyncValue>, + Object?, + Object? + >; + return element.handleCreate(ref, () => build(tagSlug: _$args)); + } +} diff --git a/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart b/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart index 404803126..d4676cd18 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/routing/news.dart'; class TagButton extends StatelessWidget { const TagButton({ @@ -28,7 +29,7 @@ class TagButton extends StatelessWidget { padding: EdgeInsets.fromLTRB(0, compact ? 0 : 8, compact ? 6 : 8, 0), child: InkWell( onTap: () { - // TODO: Navigate to tag-filtered feed + NewsTagFeedRoute(tagSlug: tagSlug, $extra: tagName).push(context); }, child: Container( constraints: BoxConstraints( From 2ec018b3030b51b1837ad7e4d9babbadd329db00 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 25 Aug 2026 22:54:30 +0400 Subject: [PATCH 19/52] chore: remove redundant comment --- mobile-app-new/lib/services/news/search_service.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile-app-new/lib/services/news/search_service.dart b/mobile-app-new/lib/services/news/search_service.dart index f50c0ab51..111c6e267 100644 --- a/mobile-app-new/lib/services/news/search_service.dart +++ b/mobile-app-new/lib/services/news/search_service.dart @@ -24,7 +24,6 @@ class NewsSearchService { void query(String term) => _repository.query(term); List _toPosts(SearchResponse response) { - // NOTE: Radio articles are hidden on iOS. final hits = Platform.isIOS ? response.hits.where((hit) => !radioArticles.contains(hit['objectID'])) : response.hits; From 13ad04e71b16daee51c5166c7cc6337b615d5aa1 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 26 Aug 2026 19:30:26 +0400 Subject: [PATCH 20/52] feat: news author views --- .../lib/models/news/author_model.dart | 2 +- mobile-app-new/lib/routing/news.dart | 19 ++- mobile-app-new/lib/routing/news.g.dart | 36 +++++ .../news/news-author/news_author_view.dart | 137 ++++++++++++++++++ .../news-author/news_author_viewmodel.dart | 11 ++ .../news-author/news_author_viewmodel.g.dart | 79 ++++++++++ .../views/news/news-feed/news_feed_view.dart | 2 +- .../views/news/news-post/news_post_view.dart | 16 +- .../news_tag_feed_view.dart | 2 +- .../views/news/widgets/news_image_view.dart | 5 +- .../post-feed-list}/post_feed_list.dart | 80 ++++++---- .../post-feed-list/post_feed_list_state.dart} | 18 ++- .../post_feed_list_state.g.dart} | 29 ++-- .../lib/ui/views/news/widgets/post_tile.dart | 9 +- 14 files changed, 384 insertions(+), 61 deletions(-) create mode 100644 mobile-app-new/lib/ui/views/news/news-author/news_author_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.g.dart rename mobile-app-new/lib/ui/views/news/{news-feed => news-tag-feed}/news_tag_feed_view.dart (85%) rename mobile-app-new/lib/ui/views/news/{news-feed => widgets/post-feed-list}/post_feed_list.dart (50%) rename mobile-app-new/lib/ui/views/news/{news-feed/post_feed_list_viewmodel.dart => widgets/post-feed-list/post_feed_list_state.dart} (66%) rename mobile-app-new/lib/ui/views/news/{news-feed/post_feed_list_viewmodel.g.dart => widgets/post-feed-list/post_feed_list_state.g.dart} (72%) diff --git a/mobile-app-new/lib/models/news/author_model.dart b/mobile-app-new/lib/models/news/author_model.dart index 6f46da637..c8ef9f895 100644 --- a/mobile-app-new/lib/models/news/author_model.dart +++ b/mobile-app-new/lib/models/news/author_model.dart @@ -30,8 +30,8 @@ abstract class Author with _$Author { required String name, String? profilePicture, Bio? bio, - // TODO: Below items are not displayed in the UI. To be added String? location, + // TODO: Below items are not displayed in the UI. To be added SocialMediaLinks? socialMediaLinks, }) = _Author; diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index 6f9846e90..928a49db7 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -3,7 +3,9 @@ import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; import 'package:mobile_app_new/ui/views/news/news-search/news_search_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-feed/news_tag_feed_view.dart'; +import 'package:mobile_app_new/models/news/author_model.dart'; +import 'package:mobile_app_new/ui/views/news/news-author/news_author_view.dart'; +import 'package:mobile_app_new/ui/views/news/news-tag-feed/news_tag_feed_view.dart'; import 'package:mobile_app_new/ui/views/news/news-view-handler/news_view_handler_view.dart'; import 'package:mobile_app_new/ui/views/news/widgets/news_image_view.dart'; @@ -15,6 +17,7 @@ const newsSearchPath = '/news/search'; const newsPostPath = '/news/:slug'; const newsImagePath = '/news/image'; const newsTagFeedPath = '/news/tag/:tagSlug'; +const newsAuthorPath = '/news/author/:username'; @TypedShellRoute( routes: [ @@ -83,6 +86,20 @@ class NewsTagFeedRoute extends GoRouteData with $NewsTagFeedRoute { NewsTagFeedView(tagSlug: tagSlug, tagName: $extra); } +@TypedGoRoute(path: newsAuthorPath) +class NewsAuthorRoute extends GoRouteData with $NewsAuthorRoute { + const NewsAuthorRoute({required this.username, this.$extra}); + + final String username; + + // NOTE: Author, when the caller already has it + final Author? $extra; + + @override + Widget build(BuildContext context, GoRouterState state) => + NewsAuthorView(username: username, author: $extra); +} + @TypedGoRoute(path: newsPostPath) class NewsPostRoute extends GoRouteData with $NewsPostRoute { const NewsPostRoute({required this.slug}); diff --git a/mobile-app-new/lib/routing/news.g.dart b/mobile-app-new/lib/routing/news.g.dart index f0bee5c76..5814dfa40 100644 --- a/mobile-app-new/lib/routing/news.g.dart +++ b/mobile-app-new/lib/routing/news.g.dart @@ -10,6 +10,7 @@ List get $appRoutes => [ $newsShellRoute, $newsImageRoute, $newsTagFeedRoute, + $newsAuthorRoute, $newsPostRoute, ]; @@ -166,6 +167,41 @@ mixin $NewsTagFeedRoute on GoRouteData { context.replace(location, extra: _self.$extra); } +RouteBase get $newsAuthorRoute => GoRouteData.$route( + path: '/news/author/:username', + hasOverriddenOnExit: false, + factory: $NewsAuthorRoute._fromState, +); + +mixin $NewsAuthorRoute on GoRouteData { + static NewsAuthorRoute _fromState(GoRouterState state) => NewsAuthorRoute( + username: state.pathParameters['username']!, + $extra: state.extra as Author?, + ); + + NewsAuthorRoute get _self => this as NewsAuthorRoute; + + @override + String get location => GoRouteData.$location( + '/news/author/${Uri.encodeComponent(_self.username)}', + ); + + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + + @override + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: _self.$extra); + + @override + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); +} + RouteBase get $newsPostRoute => GoRouteData.$route( path: '/news/:slug', hasOverriddenOnExit: false, diff --git a/mobile-app-new/lib/ui/views/news/news-author/news_author_view.dart b/mobile-app-new/lib/ui/views/news/news-author/news_author_view.dart new file mode 100644 index 000000000..4c6fb1b99 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-author/news_author_view.dart @@ -0,0 +1,137 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/models/news/author_model.dart'; +import 'package:mobile_app_new/ui/views/news/news-author/news_author_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list.dart'; + +class NewsAuthorView extends ConsumerWidget { + const NewsAuthorView({super.key, required this.username, this.author}); + + final String username; + final Author? author; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final author = this.author; + + return Scaffold( + appBar: AppBar(title: Text(author?.name ?? username)), + backgroundColor: FccColors.gray90, + body: author != null + ? _buildFeed(author) + : ref + .watch(newsAuthorProvider(username)) + .when( + loading: () => + const Center(child: CircularProgressIndicator()), + error: (error, stack) => _ErrorView( + onRetry: () => ref.invalidate(newsAuthorProvider(username)), + ), + data: _buildFeed, + ), + ); + } + + Widget _buildFeed(Author author) => PostFeedList( + authorId: author.id, + header: _AuthorDetails(author: author), + ); +} + +class _ErrorView extends StatelessWidget { + const _ErrorView({required this.onRetry}); + + final VoidCallback onRetry; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Unable to load author.', + textAlign: TextAlign.center, + ), + const SizedBox(height: 12), + TextButton(onPressed: onRetry, child: const Text('Retry')), + ], + ), + ); + } +} + +class _AuthorDetails extends StatelessWidget { + const _AuthorDetails({required this.author}); + + final Author author; + + @override + Widget build(BuildContext context) { + final location = author.location; + final bio = author.bio?.text; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 32), + child: Column( + children: [ + _buildProfilePicture(), + const SizedBox(height: 16), + Text( + author.name, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + ), + if (location != null && location.isNotEmpty) ...[ + const SizedBox(height: 4), + Text(location, style: const TextStyle(fontSize: 16)), + ], + if (bio != null && bio.isNotEmpty) ...[ + const SizedBox(height: 16), + Text( + bio, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16), + ), + ], + ], + ), + ); + } + + Widget _buildProfilePicture() { + final imageUrl = author.profilePicture; + + return Container( + width: 160, + height: 160, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: FccColors.gray80, + border: Border.fromBorderSide( + BorderSide(color: Colors.white, width: 2), + ), + ), + child: ClipOval( + child: imageUrl == null + ? Image.asset( + 'assets/images/placeholder-profile-img.png', + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: imageUrl, + fit: BoxFit.cover, + placeholder: (context, url) => + const ColoredBox(color: FccColors.gray80), + errorWidget: (context, url, error) => Image.asset( + 'assets/images/placeholder-profile-img.png', + fit: BoxFit.cover, + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.dart new file mode 100644 index 000000000..d781e18c1 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.dart @@ -0,0 +1,11 @@ +import 'package:mobile_app_new/models/news/author_model.dart'; +import 'package:mobile_app_new/services/news/api_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'news_author_viewmodel.g.dart'; + +// Only used on a cold deep link; in-app navigation hands the author over as extra +@riverpod +Future newsAuthor(Ref ref, String username) { + return ref.read(newsApiServiceProvider).getAuthor(username); +} diff --git a/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.g.dart new file mode 100644 index 000000000..4d47e22d0 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.g.dart @@ -0,0 +1,79 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_author_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsAuthor) +final newsAuthorProvider = NewsAuthorFamily._(); + +final class NewsAuthorProvider + extends $FunctionalProvider, Author, FutureOr> + with $FutureModifier, $FutureProvider { + NewsAuthorProvider._({ + required NewsAuthorFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'newsAuthorProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsAuthorHash(); + + @override + String toString() { + return r'newsAuthorProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement $createElement($ProviderPointer pointer) => + $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as String; + return newsAuthor(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is NewsAuthorProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$newsAuthorHash() => r'bdfbffd921588b7ac283c6fa2d79a2453420a862'; + +final class NewsAuthorFamily extends $Family + with $FunctionalFamilyOverride, String> { + NewsAuthorFamily._() + : super( + retry: null, + name: r'newsAuthorProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + NewsAuthorProvider call(String username) => + NewsAuthorProvider._(argument: username, from: this); + + @override + String toString() => r'newsAuthorProvider'; +} diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart index 0fd2c44d0..d966f8250 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:mobile_app_new/ui/views/news/news-feed/post_feed_list.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list.dart'; class NewsFeedView extends StatelessWidget { const NewsFeedView({super.key}); diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart index bbeccfc04..5e21c6233 100644 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart @@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; @@ -53,9 +54,15 @@ class NewsPostHeader extends StatelessWidget { post.title, style: const TextStyle(fontSize: 24, height: 1.5), ), - Text( - 'Written by ${post.author.name}', - style: const TextStyle(height: 1.5), + GestureDetector( + onTap: () => NewsAuthorRoute( + username: post.author.username, + $extra: post.author, + ).push(context), + child: Text( + 'Written by ${post.author.name}', + style: const TextStyle(height: 1.5), + ), ), if (post.tags.isNotEmpty) Wrap( @@ -231,7 +238,8 @@ class _NewsPostViewState extends ConsumerState { } void _sharePost(Post post) { - final box = _shareButtonKey.currentContext?.findRenderObject() as RenderBox?; + final box = + _shareButtonKey.currentContext?.findRenderObject() as RenderBox?; SharePlus.instance.share( ShareParams( diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_tag_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-tag-feed/news_tag_feed_view.dart similarity index 85% rename from mobile-app-new/lib/ui/views/news/news-feed/news_tag_feed_view.dart rename to mobile-app-new/lib/ui/views/news/news-tag-feed/news_tag_feed_view.dart index 8049b7ab0..8bfeafeb2 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/news_tag_feed_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-tag-feed/news_tag_feed_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/ui/views/news/news-feed/post_feed_list.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list.dart'; class NewsTagFeedView extends StatelessWidget { const NewsTagFeedView({super.key, required this.tagSlug, this.tagName}); diff --git a/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart b/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart index ac0182f40..a77263948 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart @@ -6,10 +6,7 @@ import 'package:mobile_app_new/fcc_theme.dart'; import 'package:photo_view/photo_view.dart'; class NewsImageView extends StatelessWidget { - const NewsImageView({ - super.key, - required this.imgUrl, - }); + const NewsImageView({super.key, required this.imgUrl}); final String imgUrl; diff --git a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list.dart b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart similarity index 50% rename from mobile-app-new/lib/ui/views/news/news-feed/post_feed_list.dart rename to mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart index 983085b29..e82d5fbc4 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart @@ -2,13 +2,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/ui/views/news/news-feed/post_feed_list_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart'; import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; class PostFeedList extends ConsumerStatefulWidget { - const PostFeedList({super.key, this.tagSlug = ''}); + const PostFeedList({ + super.key, + this.tagSlug = '', + this.authorId = '', + this.header, + }); final String tagSlug; + final String authorId; + + // NOTE: The header is optional and can be used to display a widget above the + // list of posts, such as author details or tag information. + final Widget? header; @override ConsumerState createState() => _PostFeedListState(); @@ -17,8 +27,10 @@ class PostFeedList extends ConsumerStatefulWidget { class _PostFeedListState extends ConsumerState { final _scrollController = ScrollController(); + bool get _hasNextPage => ref.read(_provider.notifier).hasNextPage; + NewsFeedNotifierProvider get _provider => - newsFeedProvider(tagSlug: widget.tagSlug); + newsFeedProvider(tagSlug: widget.tagSlug, authorId: widget.authorId); @override void initState() { @@ -48,47 +60,55 @@ class _PostFeedListState extends ConsumerState { child: postsAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => - _ErrorView(error: error, onRetry: () => ref.invalidate(_provider)), + _ErrorView(onRetry: () => ref.invalidate(_provider)), data: (posts) => RefreshIndicator( onRefresh: () async => ref.invalidate(_provider), backgroundColor: FccColors.gray90, color: Colors.white, - child: ListView.separated( + child: CustomScrollView( controller: _scrollController, - itemCount: posts.length + 1, - separatorBuilder: (_, _) => - const Divider(color: FccColors.gray80, thickness: 1, height: 1), - itemBuilder: (context, index) { - if (index == posts.length) { - return _buildLoadingIndicator(); - } - return PostTile( - key: ValueKey(posts[index].id), - post: posts[index], - ); - }, + slivers: [ + if (widget.header case final header?) + SliverToBoxAdapter( + child: Column( + children: [ + header, + const Divider( + color: FccColors.gray80, + thickness: 1, + height: 1, + ), + ], + ), + ), + SliverList.separated( + itemCount: posts.length + (_hasNextPage ? 1 : 0), + separatorBuilder: (_, _) => const Divider( + color: FccColors.gray80, + thickness: 1, + height: 1, + ), + itemBuilder: (context, index) => index == posts.length + ? const Padding( + padding: EdgeInsets.symmetric(vertical: 24), + child: Center(child: CircularProgressIndicator()), + ) + : PostTile( + key: ValueKey(posts[index].id), + post: posts[index], + ), + ), + ], ), ), ), ); } - - Widget _buildLoadingIndicator() { - if (!ref.read(_provider.notifier).hasNextPage) { - return const SizedBox.shrink(); - } - - return const Padding( - padding: EdgeInsets.symmetric(vertical: 24), - child: Center(child: CircularProgressIndicator()), - ); - } } class _ErrorView extends StatelessWidget { - const _ErrorView({required this.error, required this.onRetry}); + const _ErrorView({required this.onRetry}); - final Object error; final VoidCallback onRetry; @override diff --git a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.dart b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart similarity index 66% rename from mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.dart rename to mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart index 807a9a883..94712799c 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart @@ -2,7 +2,7 @@ import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/services/news/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'post_feed_list_viewmodel.g.dart'; +part 'post_feed_list_state.g.dart'; @riverpod class NewsFeedNotifier extends _$NewsFeedNotifier { @@ -15,15 +15,23 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { bool get hasNextPage => _hasNextPage; @override - FutureOr> build({String tagSlug = ''}) async { + FutureOr> build({ + String tagSlug = '', + String authorId = '', + }) async { return _fetchPage(); } Future> _fetchPage() async { final service = ref.read(newsApiServiceProvider); - final page = tagSlug.isEmpty - ? await service.getAllPosts(afterCursor: _endCursor) - : await service.getPostsByTag(tagSlug, afterCursor: _endCursor); + final PostsPage page; + if (authorId.isNotEmpty) { + page = await service.getPostsByAuthor(authorId, afterCursor: _endCursor); + } else if (tagSlug.isNotEmpty) { + page = await service.getPostsByTag(tagSlug, afterCursor: _endCursor); + } else { + page = await service.getAllPosts(afterCursor: _endCursor); + } _endCursor = page.endCursor; _hasNextPage = page.hasNextPage; diff --git a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart similarity index 72% rename from mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.g.dart rename to mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart index b8736ff50..17e033539 100644 --- a/mobile-app-new/lib/ui/views/news/news-feed/post_feed_list_viewmodel.g.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'post_feed_list_viewmodel.dart'; +part of 'post_feed_list_state.dart'; // ************************************************************************** // RiverpodGenerator @@ -16,7 +16,7 @@ final class NewsFeedNotifierProvider extends $AsyncNotifierProvider> { NewsFeedNotifierProvider._({ required NewsFeedNotifierFamily super.from, - required String super.argument, + required ({String tagSlug, String authorId}) super.argument, }) : super( retry: null, name: r'newsFeedProvider', @@ -32,7 +32,7 @@ final class NewsFeedNotifierProvider String toString() { return r'newsFeedProvider' '' - '($argument)'; + '$argument'; } @$internal @@ -50,7 +50,7 @@ final class NewsFeedNotifierProvider } } -String _$newsFeedNotifierHash() => r'8a9c5876f06d573366cae016d2d97e3aba37584b'; +String _$newsFeedNotifierHash() => r'7f00ed34d515b8035020b3a8f84c85566c4c6490'; final class NewsFeedNotifierFamily extends $Family with @@ -59,7 +59,7 @@ final class NewsFeedNotifierFamily extends $Family AsyncValue>, List, FutureOr>, - String + ({String tagSlug, String authorId}) > { NewsFeedNotifierFamily._() : super( @@ -70,18 +70,22 @@ final class NewsFeedNotifierFamily extends $Family isAutoDispose: true, ); - NewsFeedNotifierProvider call({String tagSlug = ''}) => - NewsFeedNotifierProvider._(argument: tagSlug, from: this); + NewsFeedNotifierProvider call({String tagSlug = '', String authorId = ''}) => + NewsFeedNotifierProvider._( + argument: (tagSlug: tagSlug, authorId: authorId), + from: this, + ); @override String toString() => r'newsFeedProvider'; } abstract class _$NewsFeedNotifier extends $AsyncNotifier> { - late final _$args = ref.$arg as String; - String get tagSlug => _$args; + late final _$args = ref.$arg as ({String tagSlug, String authorId}); + String get tagSlug => _$args.tagSlug; + String get authorId => _$args.authorId; - FutureOr> build({String tagSlug = ''}); + FutureOr> build({String tagSlug = '', String authorId = ''}); @$mustCallSuper @override WhenComplete runBuild() { @@ -94,6 +98,9 @@ abstract class _$NewsFeedNotifier extends $AsyncNotifier> { Object?, Object? >; - return element.handleCreate(ref, () => build(tagSlug: _$args)); + return element.handleCreate( + ref, + () => build(tagSlug: _$args.tagSlug, authorId: _$args.authorId), + ); } } diff --git a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart index 904921215..efe4f8636 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart @@ -54,7 +54,7 @@ class PostTile extends StatelessWidget { ), ), const SizedBox(height: 8), - _buildAuthorRow(), + _buildAuthorRow(context), ], ), ), @@ -93,7 +93,7 @@ class PostTile extends StatelessWidget { ); } - Widget _buildAuthorRow() { + Widget _buildAuthorRow(BuildContext context) { final author = post.author; final profileUrl = author.profilePicture; @@ -102,7 +102,10 @@ class PostTile extends StatelessWidget { Flexible( child: GestureDetector( onTap: () { - // TODO: Navigate to author profile + NewsAuthorRoute( + username: author.username, + $extra: author, + ).push(context); }, child: Row( mainAxisSize: MainAxisSize.min, From 0814787a3e499d21ec98d4a08fa228539024186b Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 26 Aug 2026 19:50:03 +0400 Subject: [PATCH 21/52] feat: back to top button for post view --- .../views/news/news-post/news_post_view.dart | 20 +++++++++++++ .../news/widgets/back_to_top_button.dart | 29 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 mobile-app-new/lib/ui/views/news/widgets/back_to_top_button.dart diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart index 5e21c6233..8b1ef52be 100644 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart @@ -9,6 +9,7 @@ import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/back_to_top_button.dart'; import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; import 'package:share_plus/share_plus.dart'; @@ -101,11 +102,13 @@ class _NewsPostViewState extends ConsumerState { final GlobalKey _shareButtonKey = GlobalKey(); bool _hasInitializedAnimation = false; + bool _showToTopButton = false; @override void initState() { super.initState(); _scrollController.addListener(_handleBottomButtonAnimation); + _scrollController.addListener(_handleToTopButtonVisibility); } @override @@ -138,6 +141,20 @@ class _NewsPostViewState extends ConsumerState { _lastScrollOffset = _scrollController.offset; } + void _handleToTopButtonVisibility() { + final shouldShow = _scrollController.offset >= 100; + if (shouldShow == _showToTopButton) return; + setState(() => _showToTopButton = shouldShow); + } + + void _goToTop() { + _scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 1000), + curve: Curves.easeInOut, + ); + } + void _initBottomButtonAnimation() { if (_hasInitializedAnimation) return; _hasInitializedAnimation = true; @@ -159,6 +176,9 @@ class _NewsPostViewState extends ConsumerState { return Scaffold( backgroundColor: FccColors.gray90, + floatingActionButton: _showToTopButton + ? BackToTopButton(onPressed: _goToTop) + : null, body: postAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) { diff --git a/mobile-app-new/lib/ui/views/news/widgets/back_to_top_button.dart b/mobile-app-new/lib/ui/views/news/widgets/back_to_top_button.dart new file mode 100644 index 000000000..901652640 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/back_to_top_button.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; + +class BackToTopButton extends StatelessWidget { + const BackToTopButton({super.key, required this.onPressed}); + + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return Padding( + // Lifts the button clear of the bottom action buttons. + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 64), + child: FloatingActionButton( + onPressed: onPressed, + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Colors.white), + borderRadius: BorderRadius.circular(100), + ), + backgroundColor: FccColors.gray80, + child: const Icon( + Icons.keyboard_arrow_up, + size: 40, + color: Colors.white, + ), + ), + ); + } +} From 8056f9f6899f93c4a35f0d4d76545e136a0171b6 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sat, 29 Aug 2026 14:34:22 +0400 Subject: [PATCH 22/52] feat: news bookmarks --- .../models/news/bookmarked_post_model.dart | 21 ++ .../news/bookmarked_post_model.freezed.dart | 280 ++++++++++++++++++ .../news/bookmarked_tutorial_model.dart | 20 -- .../news_bookmark_repository.dart | 76 +++++ .../news_bookmark_repository.g.dart | 58 ++++ mobile-app-new/lib/routing/news.dart | 22 +- mobile-app-new/lib/routing/news.g.dart | 36 +++ .../lib/services/news/bookmark_service.dart | 71 +++++ .../lib/services/news/bookmark_service.g.dart | 58 ++++ .../news_bookmark_feed_view.dart | 79 +++++ .../news_bookmark_feed_viewmodel.dart | 28 ++ .../news_bookmark_feed_viewmodel.g.dart | 62 ++++ .../news_bookmark_post_view.dart | 112 +++++++ .../views/news/news-post/news_post_view.dart | 51 +--- .../views/news/widgets/bookmark_button.dart | 31 ++ .../news/widgets/news_bottom_button.dart | 42 +++ mobile-app-new/pubspec.lock | 4 +- mobile-app-new/pubspec.yaml | 2 + 18 files changed, 981 insertions(+), 72 deletions(-) create mode 100644 mobile-app-new/lib/models/news/bookmarked_post_model.dart create mode 100644 mobile-app-new/lib/models/news/bookmarked_post_model.freezed.dart delete mode 100644 mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart create mode 100644 mobile-app-new/lib/repositories/news_bookmark_repository.dart create mode 100644 mobile-app-new/lib/repositories/news_bookmark_repository.g.dart create mode 100644 mobile-app-new/lib/services/news/bookmark_service.dart create mode 100644 mobile-app-new/lib/services/news/bookmark_service.g.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart create mode 100644 mobile-app-new/lib/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart create mode 100644 mobile-app-new/lib/ui/views/news/widgets/news_bottom_button.dart diff --git a/mobile-app-new/lib/models/news/bookmarked_post_model.dart b/mobile-app-new/lib/models/news/bookmarked_post_model.dart new file mode 100644 index 000000000..040eed627 --- /dev/null +++ b/mobile-app-new/lib/models/news/bookmarked_post_model.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/models/news/post_model.dart'; + +part 'bookmarked_post_model.freezed.dart'; + +@freezed +abstract class BookmarkedPost with _$BookmarkedPost { + const factory BookmarkedPost({ + required String id, + required String title, + required String authorName, + required String text, + }) = _BookmarkedPost; + + static BookmarkedPost fromPost(Post post) => BookmarkedPost( + id: post.id, + title: post.title, + authorName: post.author.name, + text: post.content.html, + ); +} diff --git a/mobile-app-new/lib/models/news/bookmarked_post_model.freezed.dart b/mobile-app-new/lib/models/news/bookmarked_post_model.freezed.dart new file mode 100644 index 000000000..777918dda --- /dev/null +++ b/mobile-app-new/lib/models/news/bookmarked_post_model.freezed.dart @@ -0,0 +1,280 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'bookmarked_post_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$BookmarkedPost { + + String get id; String get title; String get authorName; String get text; +/// Create a copy of BookmarkedPost +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BookmarkedPostCopyWith get copyWith => _$BookmarkedPostCopyWithImpl(this as BookmarkedPost, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BookmarkedPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.text, text) || other.text == text)); +} + + +@override +int get hashCode => Object.hash(runtimeType,id,title,authorName,text); + +@override +String toString() { + return 'BookmarkedPost(id: $id, title: $title, authorName: $authorName, text: $text)'; +} + + +} + +/// @nodoc +abstract mixin class $BookmarkedPostCopyWith<$Res> { + factory $BookmarkedPostCopyWith(BookmarkedPost value, $Res Function(BookmarkedPost) _then) = _$BookmarkedPostCopyWithImpl; +@useResult +$Res call({ + String id, String title, String authorName, String text +}); + + + + +} +/// @nodoc +class _$BookmarkedPostCopyWithImpl<$Res> + implements $BookmarkedPostCopyWith<$Res> { + _$BookmarkedPostCopyWithImpl(this._self, this._then); + + final BookmarkedPost _self; + final $Res Function(BookmarkedPost) _then; + +/// Create a copy of BookmarkedPost +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? authorName = null,Object? text = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,authorName: null == authorName ? _self.authorName : authorName // ignore: cast_nullable_to_non_nullable +as String,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [BookmarkedPost]. +extension BookmarkedPostPatterns on BookmarkedPost { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _BookmarkedPost value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _BookmarkedPost() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _BookmarkedPost value) $default,){ +final _that = this; +switch (_that) { +case _BookmarkedPost(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _BookmarkedPost value)? $default,){ +final _that = this; +switch (_that) { +case _BookmarkedPost() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String title, String authorName, String text)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _BookmarkedPost() when $default != null: +return $default(_that.id,_that.title,_that.authorName,_that.text);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String title, String authorName, String text) $default,) {final _that = this; +switch (_that) { +case _BookmarkedPost(): +return $default(_that.id,_that.title,_that.authorName,_that.text);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String title, String authorName, String text)? $default,) {final _that = this; +switch (_that) { +case _BookmarkedPost() when $default != null: +return $default(_that.id,_that.title,_that.authorName,_that.text);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _BookmarkedPost implements BookmarkedPost { + const _BookmarkedPost({required this.id, required this.title, required this.authorName, required this.text}); + + +@override final String id; +@override final String title; +@override final String authorName; +@override final String text; + +/// Create a copy of BookmarkedPost +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BookmarkedPostCopyWith<_BookmarkedPost> get copyWith => __$BookmarkedPostCopyWithImpl<_BookmarkedPost>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _BookmarkedPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.text, text) || other.text == text)); +} + + +@override +int get hashCode => Object.hash(runtimeType,id,title,authorName,text); + +@override +String toString() { + return 'BookmarkedPost(id: $id, title: $title, authorName: $authorName, text: $text)'; +} + + +} + +/// @nodoc +abstract mixin class _$BookmarkedPostCopyWith<$Res> implements $BookmarkedPostCopyWith<$Res> { + factory _$BookmarkedPostCopyWith(_BookmarkedPost value, $Res Function(_BookmarkedPost) _then) = __$BookmarkedPostCopyWithImpl; +@override @useResult +$Res call({ + String id, String title, String authorName, String text +}); + + + + +} +/// @nodoc +class __$BookmarkedPostCopyWithImpl<$Res> + implements _$BookmarkedPostCopyWith<$Res> { + __$BookmarkedPostCopyWithImpl(this._self, this._then); + + final _BookmarkedPost _self; + final $Res Function(_BookmarkedPost) _then; + +/// Create a copy of BookmarkedPost +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? authorName = null,Object? text = null,}) { + return _then(_BookmarkedPost( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,authorName: null == authorName ? _self.authorName : authorName // ignore: cast_nullable_to_non_nullable +as String,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart b/mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart deleted file mode 100644 index b9702e43e..000000000 --- a/mobile-app-new/lib/models/news/bookmarked_tutorial_model.dart +++ /dev/null @@ -1,20 +0,0 @@ -class BookmarkedTutorial { - late String id; - late String tutorialTitle; - late String authorName; - late String tutorialText; - - BookmarkedTutorial.fromMap(Map map) { - id = map['articleId']; - tutorialTitle = map['articleTitle']; - authorName = map['authorName']; - tutorialText = map['articleText']; - } - - BookmarkedTutorial({ - required this.id, - required this.tutorialTitle, - required this.authorName, - required this.tutorialText, - }); -} diff --git a/mobile-app-new/lib/repositories/news_bookmark_repository.dart b/mobile-app-new/lib/repositories/news_bookmark_repository.dart new file mode 100644 index 000000000..8423c5bd2 --- /dev/null +++ b/mobile-app-new/lib/repositories/news_bookmark_repository.dart @@ -0,0 +1,76 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'news_bookmark_repository.g.dart'; + +typedef BookmarkEntry = Map; + +const _fileName = 'bookmarked-articles.json'; +const _storeVersion = 1; + +@riverpod +NewsBookmarkRepository newsBookmarkRepository(Ref ref) { + return NewsBookmarkRepository(); +} + +class NewsBookmarkRepository { + File? _file; + + Future _resolveFile() async { + final baseDir = await getApplicationDocumentsDirectory(); + return _file ??= File(path.join(baseDir.path, 'storage', _fileName)); + } + + Future> readBookmarks() => _serialized(_read); + + Future updateBookmarks( + List Function(List current) updater, + ) { + return _serialized(() async => _write(updater(await _read()))); + } + + Future> _read() async { + final file = await _resolveFile(); + if (!await file.exists()) return []; + + try { + final decoded = jsonDecode(await file.readAsString()); + if (decoded is Map && decoded['bookmarks'] is List) { + return [ + for (final entry in decoded['bookmarks'] as List) + if (entry is Map) BookmarkEntry.from(entry), + ]; + } + log('Bookmarks: unexpected shape in ${file.path}'); + } catch (e) { + log('Bookmarks: cannot read ${file.path} ($e)'); + } + + return []; + } + + Future _write(List bookmarks) async { + final file = await _resolveFile(); + await file.parent.create(recursive: true); + + final temp = File('${file.path}.tmp'); + await temp.writeAsString( + jsonEncode({'version': _storeVersion, 'bookmarks': bookmarks}), + flush: true, + ); + await temp.rename(file.path); + } + + Future _tail = Future.value(); + + Future _serialized(Future Function() action) { + final next = _tail.then((_) => action()); + _tail = next.then((_) {}, onError: (_) {}); + return next; + } +} diff --git a/mobile-app-new/lib/repositories/news_bookmark_repository.g.dart b/mobile-app-new/lib/repositories/news_bookmark_repository.g.dart new file mode 100644 index 000000000..94b2c46b3 --- /dev/null +++ b/mobile-app-new/lib/repositories/news_bookmark_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_bookmark_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsBookmarkRepository) +final newsBookmarkRepositoryProvider = NewsBookmarkRepositoryProvider._(); + +final class NewsBookmarkRepositoryProvider + extends + $FunctionalProvider< + NewsBookmarkRepository, + NewsBookmarkRepository, + NewsBookmarkRepository + > + with $Provider { + NewsBookmarkRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsBookmarkRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsBookmarkRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + NewsBookmarkRepository create(Ref ref) { + return newsBookmarkRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(NewsBookmarkRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$newsBookmarkRepositoryHash() => + r'26f6e95a45517cb80b2397d773582152a38e58f2'; diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index 928a49db7..197421d94 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; +import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart'; import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; import 'package:mobile_app_new/ui/views/news/news-search/news_search_view.dart'; @@ -18,6 +21,7 @@ const newsPostPath = '/news/:slug'; const newsImagePath = '/news/image'; const newsTagFeedPath = '/news/tag/:tagSlug'; const newsAuthorPath = '/news/author/:username'; +const newsBookmarkPostPath = '/news/bookmarks/:id'; @TypedShellRoute( routes: [ @@ -40,9 +44,7 @@ class NewsBookmarksRoute extends GoRouteData with $NewsBookmarksRoute { @override Page buildPage(BuildContext context, GoRouterState state) => - const NoTransitionPage( - child: Center(child: Text('Bookmarks - Coming Soon')), - ); + const NoTransitionPage(child: NewsBookmarkFeedView()); } class NewsFeedRoute extends GoRouteData with $NewsFeedRoute { @@ -100,6 +102,20 @@ class NewsAuthorRoute extends GoRouteData with $NewsAuthorRoute { NewsAuthorView(username: username, author: $extra); } +@TypedGoRoute(path: newsBookmarkPostPath) +class NewsBookmarkPostRoute extends GoRouteData with $NewsBookmarkPostRoute { + const NewsBookmarkPostRoute({required this.id, required this.$extra}); + + final String id; + + // NOTE: The bookmarked post passed from feed so we don't have to fetch it again + final BookmarkedPost $extra; + + @override + Widget build(BuildContext context, GoRouterState state) => + NewsBookmarkPostView(post: $extra); +} + @TypedGoRoute(path: newsPostPath) class NewsPostRoute extends GoRouteData with $NewsPostRoute { const NewsPostRoute({required this.slug}); diff --git a/mobile-app-new/lib/routing/news.g.dart b/mobile-app-new/lib/routing/news.g.dart index 5814dfa40..49fd5ad22 100644 --- a/mobile-app-new/lib/routing/news.g.dart +++ b/mobile-app-new/lib/routing/news.g.dart @@ -11,6 +11,7 @@ List get $appRoutes => [ $newsImageRoute, $newsTagFeedRoute, $newsAuthorRoute, + $newsBookmarkPostRoute, $newsPostRoute, ]; @@ -202,6 +203,41 @@ mixin $NewsAuthorRoute on GoRouteData { context.replace(location, extra: _self.$extra); } +RouteBase get $newsBookmarkPostRoute => GoRouteData.$route( + path: '/news/bookmarks/:id', + hasOverriddenOnExit: false, + factory: $NewsBookmarkPostRoute._fromState, +); + +mixin $NewsBookmarkPostRoute on GoRouteData { + static NewsBookmarkPostRoute _fromState(GoRouterState state) => + NewsBookmarkPostRoute( + id: state.pathParameters['id']!, + $extra: state.extra as BookmarkedPost, + ); + + NewsBookmarkPostRoute get _self => this as NewsBookmarkPostRoute; + + @override + String get location => + GoRouteData.$location('/news/bookmarks/${Uri.encodeComponent(_self.id)}'); + + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + + @override + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: _self.$extra); + + @override + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); +} + RouteBase get $newsPostRoute => GoRouteData.$route( path: '/news/:slug', hasOverriddenOnExit: false, diff --git a/mobile-app-new/lib/services/news/bookmark_service.dart b/mobile-app-new/lib/services/news/bookmark_service.dart new file mode 100644 index 000000000..feff34ce6 --- /dev/null +++ b/mobile-app-new/lib/services/news/bookmark_service.dart @@ -0,0 +1,71 @@ +import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; +import 'package:mobile_app_new/repositories/news_bookmark_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'bookmark_service.g.dart'; + +// v7's on-disk key names. +const _idKey = 'articleId'; +const _titleKey = 'articleTitle'; +const _authorKey = 'authorName'; +const _textKey = 'articleText'; + +@riverpod +NewsBookmarkService newsBookmarkService(Ref ref) { + final repo = ref.watch(newsBookmarkRepositoryProvider); + return NewsBookmarkService(repo); +} + +class NewsBookmarkService { + final NewsBookmarkRepository _repo; + + NewsBookmarkService(this._repo); + + Future> getBookmarks() async { + final raw = await _repo.readBookmarks(); + return raw.reversed.map(_fromEntry).toList(); + } + + Future isBookmarked(String id) async { + final raw = await _repo.readBookmarks(); + return raw.any((entry) => entry[_idKey] == id); + } + + Future addBookmark(BookmarkedPost post) { + return _repo.updateBookmarks( + (current) => List.from(current) + ..removeWhere((entry) => entry[_idKey] == post.id) + ..add(_toEntry(post)), + ); + } + + Future removeBookmark(String id) { + return _repo.updateBookmarks( + (current) => List.from(current) + ..removeWhere((entry) => entry[_idKey] == id), + ); + } + + BookmarkEntry _toEntry(BookmarkedPost post) => { + _idKey: post.id, + _titleKey: post.title, + _authorKey: post.authorName, + _textKey: post.text, + }; + + BookmarkedPost _fromEntry(BookmarkEntry entry) => switch (entry) { + { + _idKey: final String id, + _titleKey: final String title, + _authorKey: final String authorName, + _textKey: final String text, + } => + BookmarkedPost( + id: id, + title: title, + authorName: authorName, + text: text, + ), + _ => throw FormatException('Malformed bookmark entry: $entry'), + }; +} diff --git a/mobile-app-new/lib/services/news/bookmark_service.g.dart b/mobile-app-new/lib/services/news/bookmark_service.g.dart new file mode 100644 index 000000000..8068e081b --- /dev/null +++ b/mobile-app-new/lib/services/news/bookmark_service.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bookmark_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsBookmarkService) +final newsBookmarkServiceProvider = NewsBookmarkServiceProvider._(); + +final class NewsBookmarkServiceProvider + extends + $FunctionalProvider< + NewsBookmarkService, + NewsBookmarkService, + NewsBookmarkService + > + with $Provider { + NewsBookmarkServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsBookmarkServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsBookmarkServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + NewsBookmarkService create(Ref ref) { + return newsBookmarkService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(NewsBookmarkService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$newsBookmarkServiceHash() => + r'dddde915bacf3c8e3780ae1b81dd9f6675b63b06'; diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart b/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart new file mode 100644 index 000000000..be5f07bad --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; +import 'package:mobile_app_new/routing/news.dart'; +import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart'; + +class NewsBookmarkFeedView extends ConsumerWidget { + const NewsBookmarkFeedView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bookmarks = ref.watch(newsBookmarksProvider); + + return RefreshIndicator( + backgroundColor: FccColors.gray90, + color: Colors.white, + onRefresh: () => ref.refresh(newsBookmarksProvider.future), + child: bookmarks.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => const Center( + child: Text('Unable to load bookmarks.', textAlign: TextAlign.center), + ), + data: (posts) => + posts.isEmpty ? const _EmptyState() : _BookmarkList(posts: posts), + ), + ); + } +} + +// Scrollable so RefreshIndicator still works with nothing in the list. +class _EmptyState extends StatelessWidget { + const _EmptyState(); + + @override + Widget build(BuildContext context) { + return ListView( + children: const [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 32, vertical: 96), + child: Text( + 'Bookmark tutorials to view them here', + textAlign: TextAlign.center, + ), + ), + ], + ); + } +} + +class _BookmarkList extends StatelessWidget { + const _BookmarkList({required this.posts}); + + final List posts; + + @override + Widget build(BuildContext context) { + return ListView.separated( + itemCount: posts.length, + separatorBuilder: (context, i) => const Divider(color: Colors.white), + itemBuilder: (context, index) { + final post = posts[index]; + + return ListTile( + title: Text(post.title), + trailing: const Icon(Icons.arrow_forward_ios_sharp), + subtitle: Padding( + padding: const EdgeInsets.only(top: 8), + child: Text('Written by: ${post.authorName}'), + ), + contentPadding: const EdgeInsets.all(16), + minVerticalPadding: 8, + onTap: () => + NewsBookmarkPostRoute(id: post.id, $extra: post).push(context), + ); + }, + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart new file mode 100644 index 000000000..944b75033 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart @@ -0,0 +1,28 @@ +import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; +import 'package:mobile_app_new/services/news/bookmark_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'news_bookmark_feed_viewmodel.g.dart'; + +// Single source of truth for bookmarks. The feed watches it directly and +// BookmarkButton selects its own id out of it, so a toggle anywhere is +// reflected everywhere without a manual resync. +@riverpod +class NewsBookmarksNotifier extends _$NewsBookmarksNotifier { + @override + Future> build() { + return ref.watch(newsBookmarkServiceProvider).getBookmarks(); + } + + Future toggle(BookmarkedPost post) async { + final service = ref.read(newsBookmarkServiceProvider); + + if (await service.isBookmarked(post.id)) { + await service.removeBookmark(post.id); + } else { + await service.addBookmark(post); + } + + state = AsyncData(await service.getBookmarks()); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart new file mode 100644 index 000000000..7d91e0072 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'news_bookmark_feed_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(NewsBookmarksNotifier) +final newsBookmarksProvider = NewsBookmarksNotifierProvider._(); + +final class NewsBookmarksNotifierProvider + extends + $AsyncNotifierProvider> { + NewsBookmarksNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'newsBookmarksProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsBookmarksNotifierHash(); + + @$internal + @override + NewsBookmarksNotifier create() => NewsBookmarksNotifier(); +} + +String _$newsBookmarksNotifierHash() => + r'48e14a8a3b117cf8bbb8e9de2dcd446c95162b56'; + +abstract class _$NewsBookmarksNotifier + extends $AsyncNotifier> { + FutureOr> build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = + this.ref + as $Ref>, List>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + AsyncValue>, + List + >, + AsyncValue>, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart b/mobile-app-new/lib/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart new file mode 100644 index 000000000..c8bb98c96 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; +import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; +import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/back_to_top_button.dart'; + +// Reads the HTML stored at bookmark time, so it works with no network. +class NewsBookmarkPostView extends ConsumerStatefulWidget { + const NewsBookmarkPostView({super.key, required this.post}); + + final BookmarkedPost post; + + @override + ConsumerState createState() => + _NewsBookmarkPostViewState(); +} + +class _NewsBookmarkPostViewState extends ConsumerState { + final ScrollController _scrollController = ScrollController(); + + bool _showToTopButton = false; + + // NOTE: Parsed and stored here to avoid re-parsing on every build + List? _htmlWidgets; + + @override + void initState() { + super.initState(); + _scrollController.addListener(_handleToTopButtonVisibility); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _handleToTopButtonVisibility() { + final shouldShow = _scrollController.offset >= 100; + if (shouldShow == _showToTopButton) return; + setState(() => _showToTopButton = shouldShow); + } + + void _goToTop() { + _scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 1000), + curve: Curves.easeInOut, + ); + } + + @override + Widget build(BuildContext context) { + final post = widget.post; + + final isBookmarked = ref.watch( + newsBookmarksProvider.select( + (bookmarks) => bookmarks.value?.any((b) => b.id == post.id) ?? false, + ), + ); + + final htmlWidgets = _htmlWidgets ??= HTMLParser( + context: context, + ).parse(post.text); + + return Scaffold( + backgroundColor: FccColors.gray90, + floatingActionButton: _showToTopButton + ? BackToTopButton(onPressed: _goToTop) + : null, + body: SafeArea( + child: CustomScrollView( + controller: _scrollController, + slivers: [ + SliverAppBar(title: Text(post.title)), + SliverAppBar( + pinned: true, + backgroundColor: FccColors.gray80, + automaticallyImplyLeading: false, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(8), + child: Padding( + padding: const EdgeInsets.all(8), + child: SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () => + ref.read(newsBookmarksProvider.notifier).toggle(post), + icon: Icon( + isBookmarked + ? Icons.bookmark_added + : Icons.bookmark_add_outlined, + ), + label: Text(isBookmarked ? 'Bookmarked' : 'Bookmark'), + ), + ), + ), + ), + ), + SliverList.builder( + itemCount: htmlWidgets.length, + itemBuilder: (context, index) => + Row(children: [Expanded(child: htmlWidgets[index])]), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart index 8b1ef52be..5c7ac2602 100644 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart @@ -10,6 +10,8 @@ import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; import 'package:mobile_app_new/ui/views/news/news-post/news_post_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/back_to_top_button.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/bookmark_button.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/news_bottom_button.dart'; import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; import 'package:share_plus/share_plus.dart'; @@ -284,20 +286,12 @@ class _NewsPostViewState extends ConsumerState { Row( children: [ Container(height: 150), - // TODO: Bookmark button - _BottomButton( - label: 'Bookmark', - icon: Icons.bookmark_border, - onPressed: () { - // TODO: Implement bookmark functionality - }, - rightSided: false, - ), + BookmarkButton(post: post), const SizedBox( height: 35, child: VerticalDivider(color: Colors.white, width: 0), ), - _BottomButton( + NewsBottomButton( key: _shareButtonKey, label: 'Share', icon: Icons.share, @@ -312,40 +306,3 @@ class _NewsPostViewState extends ConsumerState { ); } } - -class _BottomButton extends StatelessWidget { - const _BottomButton({ - super.key, - required this.label, - required this.onPressed, - required this.icon, - required this.rightSided, - }); - - final VoidCallback onPressed; - final String label; - final bool rightSided; - final IconData icon; - - @override - Widget build(BuildContext context) { - return Expanded( - child: ElevatedButton.icon( - icon: Icon(icon, color: Colors.white), - onPressed: onPressed, - label: Text(label), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(rightSided ? 0 : 10), - topRight: Radius.circular(rightSided ? 10 : 0), - bottomLeft: Radius.circular(rightSided ? 0 : 10), - bottomRight: Radius.circular(rightSided ? 10 : 0), - ), - ), - backgroundColor: FccColors.gray80, - ), - ), - ); - } -} diff --git a/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart b/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart new file mode 100644 index 000000000..a9e80c46f --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; +import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/news_bottom_button.dart'; + +class BookmarkButton extends ConsumerWidget { + const BookmarkButton({super.key, required this.post}); + + final Post post; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bookmarked = ref.watch( + newsBookmarksProvider.select( + (bookmarks) => + bookmarks.value?.any((b) => b.id == post.id) ?? false, + ), + ); + + return NewsBottomButton( + label: bookmarked ? 'Bookmarked' : 'Bookmark', + icon: bookmarked ? Icons.bookmark_added : Icons.bookmark_add_outlined, + onPressed: () => ref + .read(newsBookmarksProvider.notifier) + .toggle(BookmarkedPost.fromPost(post)), + rightSided: false, + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/widgets/news_bottom_button.dart b/mobile-app-new/lib/ui/views/news/widgets/news_bottom_button.dart new file mode 100644 index 000000000..2814bbdcc --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/news_bottom_button.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; + +class NewsBottomButton extends StatelessWidget { + const NewsBottomButton({ + super.key, + required this.label, + required this.onPressed, + required this.icon, + required this.rightSided, + }); + + final VoidCallback onPressed; + final String label; + final bool rightSided; + final IconData icon; + + @override + Widget build(BuildContext context) { + return Expanded( + child: ElevatedButton.icon( + icon: Icon(icon, color: Colors.white), + onPressed: onPressed, + label: FittedBox( + fit: BoxFit.scaleDown, + child: Text(label, maxLines: 1, softWrap: false), + ), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(rightSided ? 0 : 10), + topRight: Radius.circular(rightSided ? 10 : 0), + bottomLeft: Radius.circular(rightSided ? 0 : 10), + bottomRight: Radius.circular(rightSided ? 10 : 0), + ), + ), + backgroundColor: FccColors.gray80, + ), + ), + ); + } +} diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 1427eb30e..4cf47da16 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -809,7 +809,7 @@ packages: source: hosted version: "2.2.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" @@ -817,7 +817,7 @@ packages: source: hosted version: "1.9.1" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 6a3a8f157..0f89f104b 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -55,6 +55,8 @@ dependencies: algolia_helper_flutter: 1.6.0 collection: 1.19.1 html: 0.15.6 + path: 1.9.1 + path_provider: 2.1.6 dev_dependencies: flutter_test: From 284982f42f36e30cdf9bc924df716c419b07c834 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sat, 29 Aug 2026 16:56:35 +0400 Subject: [PATCH 23/52] feat: news bookmark e2e test --- .../integration_test/news/bookmark_test.dart | 125 ++++++++++++++++++ mobile-app-new/lib/main.dart | 2 +- mobile-app-new/pubspec.lock | 39 ++++++ mobile-app-new/pubspec.yaml | 4 + .../test_driver/integration_test.dart | 17 +++ 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 mobile-app-new/integration_test/news/bookmark_test.dart create mode 100644 mobile-app-new/test_driver/integration_test.dart diff --git a/mobile-app-new/integration_test/news/bookmark_test.dart b/mobile-app-new/integration_test/news/bookmark_test.dart new file mode 100644 index 000000000..dc6c5a52d --- /dev/null +++ b/mobile-app-new/integration_test/news/bookmark_test.dart @@ -0,0 +1,125 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:mobile_app_new/main.dart' as app; +import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/bookmark_button.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding(); + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + Future pumpUntilFound( + WidgetTester tester, + Finder finder, { + Duration timeout = const Duration(seconds: 60), + }) async { + final deadline = DateTime.now().add(timeout); + while (DateTime.now().isBefore(deadline)) { + await tester.pump(const Duration(milliseconds: 250)); + if (finder.evaluate().isNotEmpty) return; + } + fail('Timed out waiting for: $finder'); + } + + testWidgets('NEWS - should bookmark a post', (WidgetTester tester) async { + tester.printToConsole('Test starting'); + await app.main(); + await binding.convertFlutterSurfaceToImage(); + await tester.pumpAndSettle(); + + // Drawer -> Tutorials + await tester.tap(find.byIcon(Icons.menu)); + await tester.pumpAndSettle(); + await tester.tap(find.text('Tutorials')); + await tester.pumpAndSettle(); + + // News Feed + await pumpUntilFound(tester, find.byType(PostTile)); + await binding.takeScreenshot('news/news-feed'); + + final firstTile = find.byType(PostTile).first; + expect(firstTile, findsOneWidget); + + final postId = (tester.widget(firstTile).key! as ValueKey).value; + + await tester.tap(firstTile); + await tester.pumpAndSettle(); + + // Post detail - wait for the body, then for the bottom bar to slide in. + await pumpUntilFound(tester, find.byType(NewsPostHeader)); + await tester.pumpAndSettle(const Duration(seconds: 3)); + await binding.takeScreenshot('news/news-post'); + + final header = find.byType(NewsPostHeader); + expect(header, findsOneWidget); + + final post = tester.widget(header).post; + final title = post.title; + final author = post.author.name; + + // Bookmark the post + final bookmarkButton = find.byType(BookmarkButton); + expect(bookmarkButton, findsOneWidget); + await tester.tap(bookmarkButton); + await tester.pumpAndSettle(); + + // Back to the feed, then over to the Bookmarks tab + await tester.tap(find.byTooltip('Back')); + await tester.pumpAndSettle(); + await tester.tap(find.byTooltip('Bookmarks')); + await tester.pumpAndSettle(); + + await pumpUntilFound( + tester, + find.descendant( + of: find.byType(NewsBookmarkFeedView), + matching: find.byType(ListTile), + ), + ); + await binding.takeScreenshot('news/news-bookmark-feed'); + + // Verify that the first row in the bookmarks feed matches the post we bookmarked + final firstRow = find + .descendant( + of: find.byType(NewsBookmarkFeedView), + matching: find.byType(ListTile), + ) + .first; + expect(firstRow, findsOneWidget); + + final rowText = find.descendant(of: firstRow, matching: find.byType(Text)); + expect(tester.widget(rowText.first).data, title); + expect( + tester.widget(rowText.last).data!.replaceFirst('Written by: ', ''), + author, + ); + + // Check that the post is stored in the bookmarks JSON file + final docsDir = await getApplicationDocumentsDirectory(); + final jsonFile = File( + path.join(docsDir.path, 'storage', 'bookmarked-articles.json'), + ); + expect(await jsonFile.exists(), isTrue); + + final decoded = jsonDecode(await jsonFile.readAsString()) as Map; + final stored = (decoded['bookmarks'] as List) + .cast>() + .firstWhere( + (e) => e['articleId'] == postId, + orElse: () => {}, + ); + + expect(stored, isNotEmpty, reason: 'no entry for $postId'); + expect(stored['articleTitle'], title); + expect(stored['authorName'], author); + expect(stored['articleText'], isNotEmpty); + }); +} diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index fe5f6b732..596baf5de 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -5,7 +5,7 @@ import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/routing/router.dart'; import 'package:mobile_app_new/services/dio_service.dart'; -void main() async { +Future main() async { await dotenv.load(); await DioService().init(); diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 4cf47da16..cadf870f0 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -366,6 +366,11 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.1" + flutter_driver: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" flutter_highlight: dependency: transitive description: @@ -448,6 +453,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" glob: dependency: transitive description: @@ -608,6 +618,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: transitive description: @@ -920,6 +935,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + process: + dependency: transitive + description: + name: process + sha256: "4242ba3508d37e01808bdf71ad1d5bb93a8d671bf2e7450e6b1b353fb0808891" + url: "https://pub.dev" + source: hosted + version: "5.0.6" pub_semver: dependency: transitive description: @@ -1221,6 +1244,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" synchronized: dependency: transitive description: @@ -1389,6 +1420,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.dev" + source: hosted + version: "3.1.0" webkit_inspection_protocol: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 0f89f104b..4264b0efc 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -59,6 +59,10 @@ dependencies: path_provider: 2.1.6 dev_dependencies: + flutter_driver: + sdk: flutter + integration_test: + sdk: flutter flutter_test: sdk: flutter diff --git a/mobile-app-new/test_driver/integration_test.dart b/mobile-app-new/test_driver/integration_test.dart new file mode 100644 index 000000000..488f96541 --- /dev/null +++ b/mobile-app-new/test_driver/integration_test.dart @@ -0,0 +1,17 @@ +import 'dart:io'; + +import 'package:integration_test/integration_test_driver_extended.dart'; + +Future main() async { + try { + await integrationDriver( + onScreenshot: (String name, List bytes, [Map? _]) async { + final image = await File('screenshots/$name.png').create(recursive: true); + image.writeAsBytesSync(bytes); + return true; + }, + ); + } catch (e) { + stderr.writeln('Integration driver error: $e'); + } +} From 3d74a6632b95a9599b548a09e23b7473757a432f Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 4 Sep 2026 06:58:41 +0530 Subject: [PATCH 24/52] chore: move news repos to their own folder --- .../{news_api_repository.dart => news/api_repository.dart} | 2 +- .../{news_api_repository.g.dart => news/api_repository.g.dart} | 2 +- .../bookmark_repository.dart} | 2 +- .../bookmark_repository.g.dart} | 2 +- .../search_repository.dart} | 2 +- .../search_repository.g.dart} | 2 +- mobile-app-new/lib/services/news/api_service.dart | 2 +- mobile-app-new/lib/services/news/bookmark_service.dart | 2 +- mobile-app-new/lib/services/news/search_service.dart | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) rename mobile-app-new/lib/repositories/{news_api_repository.dart => news/api_repository.dart} (99%) rename mobile-app-new/lib/repositories/{news_api_repository.g.dart => news/api_repository.g.dart} (97%) rename mobile-app-new/lib/repositories/{news_bookmark_repository.dart => news/bookmark_repository.dart} (98%) rename mobile-app-new/lib/repositories/{news_bookmark_repository.g.dart => news/bookmark_repository.g.dart} (97%) rename mobile-app-new/lib/repositories/{news_search_repository.dart => news/search_repository.dart} (95%) rename mobile-app-new/lib/repositories/{news_search_repository.g.dart => news/search_repository.g.dart} (97%) diff --git a/mobile-app-new/lib/repositories/news_api_repository.dart b/mobile-app-new/lib/repositories/news/api_repository.dart similarity index 99% rename from mobile-app-new/lib/repositories/news_api_repository.dart rename to mobile-app-new/lib/repositories/news/api_repository.dart index f0782dea2..dc33212ca 100644 --- a/mobile-app-new/lib/repositories/news_api_repository.dart +++ b/mobile-app-new/lib/repositories/news/api_repository.dart @@ -2,7 +2,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:graphql/client.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'news_api_repository.g.dart'; +part 'api_repository.g.dart'; typedef RawPaginatedResponse = ({ String endCursor, diff --git a/mobile-app-new/lib/repositories/news_api_repository.g.dart b/mobile-app-new/lib/repositories/news/api_repository.g.dart similarity index 97% rename from mobile-app-new/lib/repositories/news_api_repository.g.dart rename to mobile-app-new/lib/repositories/news/api_repository.g.dart index 4ead1c4cd..0d7a54504 100644 --- a/mobile-app-new/lib/repositories/news_api_repository.g.dart +++ b/mobile-app-new/lib/repositories/news/api_repository.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'news_api_repository.dart'; +part of 'api_repository.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/repositories/news_bookmark_repository.dart b/mobile-app-new/lib/repositories/news/bookmark_repository.dart similarity index 98% rename from mobile-app-new/lib/repositories/news_bookmark_repository.dart rename to mobile-app-new/lib/repositories/news/bookmark_repository.dart index 8423c5bd2..c86594399 100644 --- a/mobile-app-new/lib/repositories/news_bookmark_repository.dart +++ b/mobile-app-new/lib/repositories/news/bookmark_repository.dart @@ -6,7 +6,7 @@ import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'news_bookmark_repository.g.dart'; +part 'bookmark_repository.g.dart'; typedef BookmarkEntry = Map; diff --git a/mobile-app-new/lib/repositories/news_bookmark_repository.g.dart b/mobile-app-new/lib/repositories/news/bookmark_repository.g.dart similarity index 97% rename from mobile-app-new/lib/repositories/news_bookmark_repository.g.dart rename to mobile-app-new/lib/repositories/news/bookmark_repository.g.dart index 94b2c46b3..896216548 100644 --- a/mobile-app-new/lib/repositories/news_bookmark_repository.g.dart +++ b/mobile-app-new/lib/repositories/news/bookmark_repository.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'news_bookmark_repository.dart'; +part of 'bookmark_repository.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/repositories/news_search_repository.dart b/mobile-app-new/lib/repositories/news/search_repository.dart similarity index 95% rename from mobile-app-new/lib/repositories/news_search_repository.dart rename to mobile-app-new/lib/repositories/news/search_repository.dart index 5f3235436..3a19152dc 100644 --- a/mobile-app-new/lib/repositories/news_search_repository.dart +++ b/mobile-app-new/lib/repositories/news/search_repository.dart @@ -4,7 +4,7 @@ import 'package:algolia_helper_flutter/algolia_helper_flutter.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'news_search_repository.g.dart'; +part 'search_repository.g.dart'; const _indexName = 'news'; diff --git a/mobile-app-new/lib/repositories/news_search_repository.g.dart b/mobile-app-new/lib/repositories/news/search_repository.g.dart similarity index 97% rename from mobile-app-new/lib/repositories/news_search_repository.g.dart rename to mobile-app-new/lib/repositories/news/search_repository.g.dart index 1e566c8a0..d25e28234 100644 --- a/mobile-app-new/lib/repositories/news_search_repository.g.dart +++ b/mobile-app-new/lib/repositories/news/search_repository.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'news_search_repository.dart'; +part of 'search_repository.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/services/news/api_service.dart b/mobile-app-new/lib/services/news/api_service.dart index c8280e3ff..ef4a6c146 100644 --- a/mobile-app-new/lib/services/news/api_service.dart +++ b/mobile-app-new/lib/services/news/api_service.dart @@ -1,6 +1,6 @@ import 'package:mobile_app_new/models/news/author_model.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; -import 'package:mobile_app_new/repositories/news_api_repository.dart'; +import 'package:mobile_app_new/repositories/news/api_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'api_service.g.dart'; diff --git a/mobile-app-new/lib/services/news/bookmark_service.dart b/mobile-app-new/lib/services/news/bookmark_service.dart index feff34ce6..9d4d77d53 100644 --- a/mobile-app-new/lib/services/news/bookmark_service.dart +++ b/mobile-app-new/lib/services/news/bookmark_service.dart @@ -1,5 +1,5 @@ import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; -import 'package:mobile_app_new/repositories/news_bookmark_repository.dart'; +import 'package:mobile_app_new/repositories/news/bookmark_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'bookmark_service.g.dart'; diff --git a/mobile-app-new/lib/services/news/search_service.dart b/mobile-app-new/lib/services/news/search_service.dart index 111c6e267..573e38e26 100644 --- a/mobile-app-new/lib/services/news/search_service.dart +++ b/mobile-app-new/lib/services/news/search_service.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:algolia_helper_flutter/algolia_helper_flutter.dart'; import 'package:mobile_app_new/constants/radio_articles.dart'; import 'package:mobile_app_new/models/news/search_post_model.dart'; -import 'package:mobile_app_new/repositories/news_search_repository.dart'; +import 'package:mobile_app_new/repositories/news/search_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'search_service.g.dart'; From ab0449c810259b5364c3d8cad56748fb0afd6fef Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 6 Sep 2026 08:22:47 +0530 Subject: [PATCH 25/52] chore: final cleanup of news section --- mobile-app-new/analysis_options.yaml | 2 +- mobile-app-new/devtools_options.yaml | 3 + .../integration_test/news/bookmark_test.dart | 4 +- .../lib/models/news/post_model.dart | 104 ------- mobile-app-new/lib/routing/news.dart | 22 +- .../lib/services/news/bookmark_service.dart | 12 +- .../lib/ui/core/widgets/error_retry.dart | 24 ++ .../author_feed_view.dart} | 32 +- .../author_feed_viewmodel.dart} | 2 +- .../author_feed_viewmodel.g.dart} | 2 +- .../bookmark_feed_view.dart} | 8 +- .../bookmark_feed_viewmodel.dart} | 5 +- .../bookmark_feed_viewmodel.g.dart} | 2 +- .../bookmark_post_view.dart} | 2 +- .../feed_view.dart} | 0 .../image_view.dart} | 0 .../news/news-post/news_post_viewmodel.dart | 14 - .../news/news-post/news_post_viewmodel.g.dart | 99 ------ .../news_view_handler_view.dart | 52 ---- .../lib/ui/views/news/news_shell.dart | 53 ++++ .../post_view.dart} | 36 +-- .../ui/views/news/post/post_viewmodel.dart | 10 + .../ui/views/news/post/post_viewmodel.g.dart | 79 +++++ .../search_view.dart} | 2 +- .../search_viewmodel.dart} | 2 +- .../search_viewmodel.g.dart} | 2 +- .../tag_feed_view.dart} | 0 .../views/news/widgets/bookmark_button.dart | 7 +- ..._bottom_button.dart => bottom_button.dart} | 0 .../post-feed-list/post_feed_list.dart | 84 ++--- .../post-feed-list/post_feed_list_state.dart | 82 +++-- .../post_feed_list_state.freezed.dart | 287 ++++++++++++++++++ .../post_feed_list_state.g.dart | 20 +- mobile-app-new/pubspec.lock | 32 ++ mobile-app-new/pubspec.yaml | 1 + 35 files changed, 652 insertions(+), 434 deletions(-) create mode 100644 mobile-app-new/devtools_options.yaml create mode 100644 mobile-app-new/lib/ui/core/widgets/error_retry.dart rename mobile-app-new/lib/ui/views/news/{news-author/news_author_view.dart => author-feed/author_feed_view.dart} (80%) rename mobile-app-new/lib/ui/views/news/{news-author/news_author_viewmodel.dart => author-feed/author_feed_viewmodel.dart} (91%) rename mobile-app-new/lib/ui/views/news/{news-author/news_author_viewmodel.g.dart => author-feed/author_feed_viewmodel.g.dart} (98%) rename mobile-app-new/lib/ui/views/news/{news-bookmark-feed/news_bookmark_feed_view.dart => bookmark-feed/bookmark_feed_view.dart} (87%) rename mobile-app-new/lib/ui/views/news/{news-bookmark-feed/news_bookmark_feed_viewmodel.dart => bookmark-feed/bookmark_feed_viewmodel.dart} (75%) rename mobile-app-new/lib/ui/views/news/{news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart => bookmark-feed/bookmark_feed_viewmodel.g.dart} (97%) rename mobile-app-new/lib/ui/views/news/{news-bookmark-post/news_bookmark_post_view.dart => bookmark-post/bookmark_post_view.dart} (97%) rename mobile-app-new/lib/ui/views/news/{news-feed/news_feed_view.dart => feed/feed_view.dart} (100%) rename mobile-app-new/lib/ui/views/news/{widgets/news_image_view.dart => image/image_view.dart} (100%) delete mode 100644 mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart delete mode 100644 mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart delete mode 100644 mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart create mode 100644 mobile-app-new/lib/ui/views/news/news_shell.dart rename mobile-app-new/lib/ui/views/news/{news-post/news_post_view.dart => post/post_view.dart} (90%) create mode 100644 mobile-app-new/lib/ui/views/news/post/post_viewmodel.dart create mode 100644 mobile-app-new/lib/ui/views/news/post/post_viewmodel.g.dart rename mobile-app-new/lib/ui/views/news/{news-search/news_search_view.dart => search/search_view.dart} (96%) rename mobile-app-new/lib/ui/views/news/{news-search/news_search_viewmodel.dart => search/search_viewmodel.dart} (95%) rename mobile-app-new/lib/ui/views/news/{news-search/news_search_viewmodel.g.dart => search/search_viewmodel.g.dart} (98%) rename mobile-app-new/lib/ui/views/news/{news-tag-feed/news_tag_feed_view.dart => tag-feed/tag_feed_view.dart} (100%) rename mobile-app-new/lib/ui/views/news/widgets/{news_bottom_button.dart => bottom_button.dart} (100%) create mode 100644 mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.freezed.dart diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml index 34217f3ba..ade444ad8 100644 --- a/mobile-app-new/analysis_options.yaml +++ b/mobile-app-new/analysis_options.yaml @@ -33,4 +33,4 @@ analyzer: strict-casts: true plugins: - riverpod_lint: 3.1.8 + riverpod_lint: 3.1.4 diff --git a/mobile-app-new/devtools_options.yaml b/mobile-app-new/devtools_options.yaml new file mode 100644 index 000000000..fa0b357c4 --- /dev/null +++ b/mobile-app-new/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/mobile-app-new/integration_test/news/bookmark_test.dart b/mobile-app-new/integration_test/news/bookmark_test.dart index dc6c5a52d..aa5460410 100644 --- a/mobile-app-new/integration_test/news/bookmark_test.dart +++ b/mobile-app-new/integration_test/news/bookmark_test.dart @@ -5,8 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mobile_app_new/main.dart' as app; -import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; +import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/post/post_view.dart'; import 'package:mobile_app_new/ui/views/news/widgets/bookmark_button.dart'; import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; import 'package:path/path.dart' as path; diff --git a/mobile-app-new/lib/models/news/post_model.dart b/mobile-app-new/lib/models/news/post_model.dart index cdb3d4c14..019906178 100644 --- a/mobile-app-new/lib/models/news/post_model.dart +++ b/mobile-app-new/lib/models/news/post_model.dart @@ -4,110 +4,6 @@ import 'author_model.dart'; part 'post_model.freezed.dart'; part 'post_model.g.dart'; -// class Tutorial { -// final String id; -// final String slug; -// final String title; -// final String? featureImage; -// final String? profileImage; -// final String authorId; -// final String authorName; -// final String authorSlug; -// final String? createdAt; -// final String? url; -// final String? text; - -// Tutorial({ -// required this.id, -// required this.slug, -// required this.featureImage, -// required this.title, -// required this.profileImage, -// required this.authorId, -// required this.authorName, -// required this.authorSlug, -// this.createdAt, -// this.url, -// this.text, -// }); - -// // static List returnTags( -// // list, { -// // bool compact = false, -// // }) { -// // List tags = []; - -// // for (int i = 0; i < list.length; i++) { -// // tags.add(TagButton( -// // tagName: list[i]['name'], -// // tagSlug: list[i]['slug'] ?? list[i]['id'], -// // key: UniqueKey(), -// // compact: compact, -// // )); -// // } -// // return tags; -// // } - -// // this factory is for the endpoint where all tutorial thumbnails are received - -// // factory Tutorial.fromJson(dynamic data) { -// // return Tutorial( -// // createdAt: data['publishedAt'], -// // featureImage: data['coverImage']['url'], -// // title: data['title'], -// // profileImage: data['author']['profilePicture'], -// // authorId: data['author']['id'], -// // authorName: data['author']['name'], -// // authorSlug: data['author']['username'], -// // tagNames: returnTags(data['tags']), -// // rawTags: data['tags'] ?? [], -// // id: data['id'], -// // slug: data['slug'], -// // ); -// // } - -// // factory Tutorial.fromSearch(Map data) { -// // return Tutorial( -// // createdAt: data['publishedAt'], -// // featureImage: data['featureImage'], -// // title: data['title'], -// // profileImage: data['author']['profileImage'], -// // authorId: data['author']['id'], -// // authorName: data['author']['name'], -// // authorSlug: returnSlug(data['author']['url']), -// // tagNames: returnTags(data['tags']), -// // rawTags: data['tags'] ?? [], -// // id: data['objectID'], -// // slug: data['slug'], -// // ); -// // } - -// // static String returnSlug(String url) { -// // List splitUrl = url.split('/'); - -// // return splitUrl[splitUrl.length - 2]; -// // } - -// // this is factory is for the post view - -// // factory Tutorial.toPostFromJson(Map json) { -// // return Tutorial( -// // authorId: json['author']['id'], -// // authorName: json['author']['name'], -// // authorSlug: json['author']['username'], -// // profileImage: json['author']['profilePicture'], -// // tagNames: returnTags(json['tags']), -// // rawTags: json['tags'] ?? [], -// // id: json['id'], -// // title: json['title'], -// // url: json['url'], -// // text: json['content']['html'], -// // featureImage: json['coverImage']['url'], -// // slug: json['slug'], -// // ); -// // } -// } - @freezed abstract class Tag with _$Tag { const factory Tag({ diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index 197421d94..a22744bf7 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; -import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-feed/news_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-post/news_post_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-search/news_search_view.dart'; +import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/bookmark-post/bookmark_post_view.dart'; +import 'package:mobile_app_new/ui/views/news/feed/feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/post/post_view.dart'; +import 'package:mobile_app_new/ui/views/news/search/search_view.dart'; import 'package:mobile_app_new/models/news/author_model.dart'; -import 'package:mobile_app_new/ui/views/news/news-author/news_author_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-tag-feed/news_tag_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/news-view-handler/news_view_handler_view.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/news_image_view.dart'; +import 'package:mobile_app_new/ui/views/news/author-feed/author_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/tag-feed/tag_feed_view.dart'; +import 'package:mobile_app_new/ui/views/news/news_shell.dart'; +import 'package:mobile_app_new/ui/views/news/image/image_view.dart'; part 'news.g.dart'; @@ -35,7 +35,7 @@ class NewsShellRoute extends ShellRouteData { @override Widget builder(BuildContext context, GoRouterState state, Widget navigator) { - return NewsViewHandlerView(child: navigator); + return NewsShell(child: navigator); } } @@ -99,7 +99,7 @@ class NewsAuthorRoute extends GoRouteData with $NewsAuthorRoute { @override Widget build(BuildContext context, GoRouterState state) => - NewsAuthorView(username: username, author: $extra); + NewsAuthorFeedView(username: username, author: $extra); } @TypedGoRoute(path: newsBookmarkPostPath) diff --git a/mobile-app-new/lib/services/news/bookmark_service.dart b/mobile-app-new/lib/services/news/bookmark_service.dart index 9d4d77d53..f33e1efcf 100644 --- a/mobile-app-new/lib/services/news/bookmark_service.dart +++ b/mobile-app-new/lib/services/news/bookmark_service.dart @@ -41,8 +41,9 @@ class NewsBookmarkService { Future removeBookmark(String id) { return _repo.updateBookmarks( - (current) => List.from(current) - ..removeWhere((entry) => entry[_idKey] == id), + (current) => + List.from(current) + ..removeWhere((entry) => entry[_idKey] == id), ); } @@ -60,12 +61,7 @@ class NewsBookmarkService { _authorKey: final String authorName, _textKey: final String text, } => - BookmarkedPost( - id: id, - title: title, - authorName: authorName, - text: text, - ), + BookmarkedPost(id: id, title: title, authorName: authorName, text: text), _ => throw FormatException('Malformed bookmark entry: $entry'), }; } diff --git a/mobile-app-new/lib/ui/core/widgets/error_retry.dart b/mobile-app-new/lib/ui/core/widgets/error_retry.dart new file mode 100644 index 000000000..1a1a3b488 --- /dev/null +++ b/mobile-app-new/lib/ui/core/widgets/error_retry.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class ErrorRetry extends StatelessWidget { + const ErrorRetry({super.key, required this.message, this.onRetry}); + + final String message; + final VoidCallback? onRetry; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(message, textAlign: TextAlign.center), + if (onRetry != null) ...[ + const SizedBox(height: 12), + TextButton(onPressed: onRetry, child: const Text('Retry')), + ], + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-author/news_author_view.dart b/mobile-app-new/lib/ui/views/news/author-feed/author_feed_view.dart similarity index 80% rename from mobile-app-new/lib/ui/views/news/news-author/news_author_view.dart rename to mobile-app-new/lib/ui/views/news/author-feed/author_feed_view.dart index 4c6fb1b99..e528bd72e 100644 --- a/mobile-app-new/lib/ui/views/news/news-author/news_author_view.dart +++ b/mobile-app-new/lib/ui/views/news/author-feed/author_feed_view.dart @@ -4,11 +4,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/author_model.dart'; -import 'package:mobile_app_new/ui/views/news/news-author/news_author_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/author-feed/author_feed_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list.dart'; +import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; -class NewsAuthorView extends ConsumerWidget { - const NewsAuthorView({super.key, required this.username, this.author}); +class NewsAuthorFeedView extends ConsumerWidget { + const NewsAuthorFeedView({super.key, required this.username, this.author}); final String username; final Author? author; @@ -27,7 +28,8 @@ class NewsAuthorView extends ConsumerWidget { .when( loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => _ErrorView( + error: (error, stack) => ErrorRetry( + message: 'Unable to load author.', onRetry: () => ref.invalidate(newsAuthorProvider(username)), ), data: _buildFeed, @@ -41,28 +43,6 @@ class NewsAuthorView extends ConsumerWidget { ); } -class _ErrorView extends StatelessWidget { - const _ErrorView({required this.onRetry}); - - final VoidCallback onRetry; - - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'Unable to load author.', - textAlign: TextAlign.center, - ), - const SizedBox(height: 12), - TextButton(onPressed: onRetry, child: const Text('Retry')), - ], - ), - ); - } -} class _AuthorDetails extends StatelessWidget { const _AuthorDetails({required this.author}); diff --git a/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.dart b/mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.dart similarity index 91% rename from mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.dart rename to mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.dart index d781e18c1..7cdfa41b4 100644 --- a/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.dart +++ b/mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.dart @@ -2,7 +2,7 @@ import 'package:mobile_app_new/models/news/author_model.dart'; import 'package:mobile_app_new/services/news/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'news_author_viewmodel.g.dart'; +part 'author_feed_viewmodel.g.dart'; // Only used on a cold deep link; in-app navigation hands the author over as extra @riverpod diff --git a/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.g.dart similarity index 98% rename from mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.g.dart rename to mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.g.dart index 4d47e22d0..022778cbe 100644 --- a/mobile-app-new/lib/ui/views/news/news-author/news_author_viewmodel.g.dart +++ b/mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'news_author_viewmodel.dart'; +part of 'author_feed_viewmodel.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart b/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_view.dart similarity index 87% rename from mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart rename to mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_view.dart index be5f07bad..dff8ab962 100644 --- a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_view.dart +++ b/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_view.dart @@ -3,7 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; class NewsBookmarkFeedView extends ConsumerWidget { const NewsBookmarkFeedView({super.key}); @@ -18,8 +19,9 @@ class NewsBookmarkFeedView extends ConsumerWidget { onRefresh: () => ref.refresh(newsBookmarksProvider.future), child: bookmarks.when( loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => const Center( - child: Text('Unable to load bookmarks.', textAlign: TextAlign.center), + error: (error, stack) => ErrorRetry( + message: 'Unable to load bookmarks.', + onRetry: () => ref.invalidate(newsBookmarksProvider), ), data: (posts) => posts.isEmpty ? const _EmptyState() : _BookmarkList(posts: posts), diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart b/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart similarity index 75% rename from mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart rename to mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart index 944b75033..7338d4a41 100644 --- a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart +++ b/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart @@ -2,11 +2,8 @@ import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; import 'package:mobile_app_new/services/news/bookmark_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'news_bookmark_feed_viewmodel.g.dart'; +part 'bookmark_feed_viewmodel.g.dart'; -// Single source of truth for bookmarks. The feed watches it directly and -// BookmarkButton selects its own id out of it, so a toggle anywhere is -// reflected everywhere without a manual resync. @riverpod class NewsBookmarksNotifier extends _$NewsBookmarksNotifier { @override diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.g.dart similarity index 97% rename from mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart rename to mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.g.dart index 7d91e0072..e28d8197b 100644 --- a/mobile-app-new/lib/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.g.dart +++ b/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'news_bookmark_feed_viewmodel.dart'; +part of 'bookmark_feed_viewmodel.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart b/mobile-app-new/lib/ui/views/news/bookmark-post/bookmark_post_view.dart similarity index 97% rename from mobile-app-new/lib/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart rename to mobile-app-new/lib/ui/views/news/bookmark-post/bookmark_post_view.dart index c8bb98c96..c4c814c67 100644 --- a/mobile-app-new/lib/ui/views/news/news-bookmark-post/news_bookmark_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/bookmark-post/bookmark_post_view.dart @@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; -import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/back_to_top_button.dart'; +import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart'; // Reads the HTML stored at bookmark time, so it works with no network. class NewsBookmarkPostView extends ConsumerStatefulWidget { diff --git a/mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart b/mobile-app-new/lib/ui/views/news/feed/feed_view.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/news-feed/news_feed_view.dart rename to mobile-app-new/lib/ui/views/news/feed/feed_view.dart diff --git a/mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart b/mobile-app-new/lib/ui/views/news/image/image_view.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/widgets/news_image_view.dart rename to mobile-app-new/lib/ui/views/news/image/image_view.dart diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart deleted file mode 100644 index 4849f1d92..000000000 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:mobile_app_new/models/news/post_model.dart'; -import 'package:mobile_app_new/services/news/api_service.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'news_post_viewmodel.g.dart'; - -@riverpod -class NewsPostNotifier extends _$NewsPostNotifier { - @override - FutureOr build(String slug) async { - final service = ref.read(newsApiServiceProvider); - return service.getPostBySlug(slug); - } -} diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart deleted file mode 100644 index 371fa747f..000000000 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_viewmodel.g.dart +++ /dev/null @@ -1,99 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'news_post_viewmodel.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint, type=warning - -@ProviderFor(NewsPostNotifier) -final newsPostProvider = NewsPostNotifierFamily._(); - -final class NewsPostNotifierProvider - extends $AsyncNotifierProvider { - NewsPostNotifierProvider._({ - required NewsPostNotifierFamily super.from, - required String super.argument, - }) : super( - retry: null, - name: r'newsPostProvider', - isAutoDispose: true, - dependencies: null, - $allTransitiveDependencies: null, - ); - - @override - String debugGetCreateSourceHash() => _$newsPostNotifierHash(); - - @override - String toString() { - return r'newsPostProvider' - '' - '($argument)'; - } - - @$internal - @override - NewsPostNotifier create() => NewsPostNotifier(); - - @override - bool operator ==(Object other) { - return other is NewsPostNotifierProvider && other.argument == argument; - } - - @override - int get hashCode { - return argument.hashCode; - } -} - -String _$newsPostNotifierHash() => r'4bd3a49efc3dc02afdc6162c81e9c16f333ef143'; - -final class NewsPostNotifierFamily extends $Family - with - $ClassFamilyOverride< - NewsPostNotifier, - AsyncValue, - Post, - FutureOr, - String - > { - NewsPostNotifierFamily._() - : super( - retry: null, - name: r'newsPostProvider', - dependencies: null, - $allTransitiveDependencies: null, - isAutoDispose: true, - ); - - NewsPostNotifierProvider call(String slug) => - NewsPostNotifierProvider._(argument: slug, from: this); - - @override - String toString() => r'newsPostProvider'; -} - -abstract class _$NewsPostNotifier extends $AsyncNotifier { - late final _$args = ref.$arg as String; - String get slug => _$args; - - FutureOr build(String slug); - @$mustCallSuper - @override - WhenComplete runBuild() { - final ref = this.ref as $Ref, Post>; - final element = - ref.element - as $ClassProviderElement< - AnyNotifier, Post>, - AsyncValue, - Object?, - Object? - >; - return element.handleCreate(ref, () => build(_$args)); - } -} diff --git a/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart b/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart deleted file mode 100644 index 0810ec987..000000000 --- a/mobile-app-new/lib/ui/views/news/news-view-handler/news_view_handler_view.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; - -class NewsViewHandlerView extends StatelessWidget { - const NewsViewHandlerView({super.key, required this.child}); - - final Widget child; - - static const _titles = ['Bookmarks', 'Tutorials', 'Search']; - static const _paths = [newsBookmarksPath, newsFeedPath, newsSearchPath]; - static const _feedIndex = 1; - - int _currentIndex(BuildContext context) { - final location = GoRouterState.of(context).uri.path; - final index = _paths.indexOf(location); - return index == -1 ? _feedIndex : index; - } - - @override - Widget build(BuildContext context) { - final index = _currentIndex(context); - - return Scaffold( - appBar: AppBar(title: Text(_titles[index])), - drawer: const DrawerWidget(), - body: child, - bottomNavigationBar: BottomNavigationBar( - currentIndex: index, - onTap: (i) => context.go(_paths[i]), - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.bookmark_outline_sharp), - label: 'Bookmarks', - tooltip: 'Bookmarks', - ), - BottomNavigationBarItem( - icon: Icon(Icons.article_sharp), - label: 'Tutorials', - tooltip: 'Tutorials', - ), - BottomNavigationBarItem( - icon: Icon(Icons.search_sharp), - label: 'Search', - tooltip: 'Search', - ), - ], - ), - ); - } -} diff --git a/mobile-app-new/lib/ui/views/news/news_shell.dart b/mobile-app-new/lib/ui/views/news/news_shell.dart new file mode 100644 index 000000000..f358da941 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/news_shell.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/routing/news.dart'; +import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; + +typedef _Tab = ({String title, String path, IconData icon}); + +class NewsShell extends StatelessWidget { + const NewsShell({super.key, required this.child}); + + final Widget child; + + static const List<_Tab> _tabs = [ + ( + title: 'Bookmarks', + path: newsBookmarksPath, + icon: Icons.bookmark_outline_sharp, + ), + (title: 'Tutorials', path: newsFeedPath, icon: Icons.article_sharp), + (title: 'Search', path: newsSearchPath, icon: Icons.search_sharp), + ]; + + static const _feedIndex = 1; + + int _currentIndex(BuildContext context) { + final location = GoRouterState.of(context).uri.path; + final index = _tabs.indexWhere((tab) => tab.path == location); + return index == -1 ? _feedIndex : index; + } + + @override + Widget build(BuildContext context) { + final index = _currentIndex(context); + + return Scaffold( + appBar: AppBar(title: Text(_tabs[index].title)), + drawer: const DrawerWidget(), + body: child, + bottomNavigationBar: BottomNavigationBar( + currentIndex: index, + onTap: (i) => context.go(_tabs[i].path), + items: [ + for (final tab in _tabs) + BottomNavigationBarItem( + icon: Icon(tab.icon), + label: tab.title, + tooltip: tab.title, + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart b/mobile-app-new/lib/ui/views/news/post/post_view.dart similarity index 90% rename from mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart rename to mobile-app-new/lib/ui/views/news/post/post_view.dart index 5c7ac2602..f41c83b36 100644 --- a/mobile-app-new/lib/ui/views/news/news-post/news_post_view.dart +++ b/mobile-app-new/lib/ui/views/news/post/post_view.dart @@ -8,12 +8,13 @@ import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; -import 'package:mobile_app_new/ui/views/news/news-post/news_post_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/post/post_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/back_to_top_button.dart'; import 'package:mobile_app_new/ui/views/news/widgets/bookmark_button.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/news_bottom_button.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/bottom_button.dart'; import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; class NewsPostHeader extends StatelessWidget { const NewsPostHeader({super.key, required this.post}); @@ -106,6 +107,9 @@ class _NewsPostViewState extends ConsumerState { bool _hasInitializedAnimation = false; bool _showToTopButton = false; + // NOTE: Parsed and stored here to avoid re-parsing on every build + List? _htmlWidgets; + @override void initState() { super.initState(); @@ -185,23 +189,15 @@ class _NewsPostViewState extends ConsumerState { loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) { log('Error loading post: $error'); - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Unable to load post.', textAlign: TextAlign.center), - const SizedBox(height: 12), - TextButton( - onPressed: () => - ref.invalidate(newsPostProvider(widget.slug)), - child: const Text('Retry'), - ), - ], - ), + return ErrorRetry( + message: 'Unable to load post.', + onRetry: () => ref.invalidate(newsPostProvider(widget.slug)), ); }, data: (post) { - final htmlWidgets = _buildLazyLoadedHtml(post); + final htmlWidgets = _htmlWidgets ??= _parseHtml(post); + _initBottomButtonAnimation(); + return Stack( children: [ ListView.builder( @@ -220,10 +216,8 @@ class _NewsPostViewState extends ConsumerState { ); } - List _buildLazyLoadedHtml(Post post) { - HTMLParser htmlParser = HTMLParser(context: context); - - List elements = htmlParser.parse(post.content.html); + List _parseHtml(Post post) { + final elements = HTMLParser(context: context).parse(post.content.html); elements.insert( 0, @@ -254,8 +248,6 @@ class _NewsPostViewState extends ConsumerState { elements.add(const SizedBox(height: 100)); - _initBottomButtonAnimation(); - return elements; } diff --git a/mobile-app-new/lib/ui/views/news/post/post_viewmodel.dart b/mobile-app-new/lib/ui/views/news/post/post_viewmodel.dart new file mode 100644 index 000000000..74ee1de28 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/post/post_viewmodel.dart @@ -0,0 +1,10 @@ +import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/services/news/api_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'post_viewmodel.g.dart'; + +@riverpod +Future newsPost(Ref ref, String slug) { + return ref.read(newsApiServiceProvider).getPostBySlug(slug); +} diff --git a/mobile-app-new/lib/ui/views/news/post/post_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/post/post_viewmodel.g.dart new file mode 100644 index 000000000..5e0656569 --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/post/post_viewmodel.g.dart @@ -0,0 +1,79 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'post_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(newsPost) +final newsPostProvider = NewsPostFamily._(); + +final class NewsPostProvider + extends $FunctionalProvider, Post, FutureOr> + with $FutureModifier, $FutureProvider { + NewsPostProvider._({ + required NewsPostFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'newsPostProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$newsPostHash(); + + @override + String toString() { + return r'newsPostProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement $createElement($ProviderPointer pointer) => + $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as String; + return newsPost(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is NewsPostProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$newsPostHash() => r'26d73110a1b22400e8030d5543a43f78104a83f3'; + +final class NewsPostFamily extends $Family + with $FunctionalFamilyOverride, String> { + NewsPostFamily._() + : super( + retry: null, + name: r'newsPostProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + NewsPostProvider call(String slug) => + NewsPostProvider._(argument: slug, from: this); + + @override + String toString() => r'newsPostProvider'; +} diff --git a/mobile-app-new/lib/ui/views/news/news-search/news_search_view.dart b/mobile-app-new/lib/ui/views/news/search/search_view.dart similarity index 96% rename from mobile-app-new/lib/ui/views/news/news-search/news_search_view.dart rename to mobile-app-new/lib/ui/views/news/search/search_view.dart index da278aadc..62b936cf4 100644 --- a/mobile-app-new/lib/ui/views/news/news-search/news_search_view.dart +++ b/mobile-app-new/lib/ui/views/news/search/search_view.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/models/news/search_post_model.dart'; -import 'package:mobile_app_new/ui/views/news/news-search/news_search_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/search/search_viewmodel.dart'; import 'package:mobile_app_new/ui/views/news/widgets/search_result_tile.dart'; class NewsSearchView extends ConsumerStatefulWidget { diff --git a/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.dart b/mobile-app-new/lib/ui/views/news/search/search_viewmodel.dart similarity index 95% rename from mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.dart rename to mobile-app-new/lib/ui/views/news/search/search_viewmodel.dart index a1ef2716c..673227107 100644 --- a/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.dart +++ b/mobile-app-new/lib/ui/views/news/search/search_viewmodel.dart @@ -2,7 +2,7 @@ import 'package:mobile_app_new/models/news/search_post_model.dart'; import 'package:mobile_app_new/services/news/search_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'news_search_viewmodel.g.dart'; +part 'search_viewmodel.g.dart'; @riverpod class NewsSearchNotifier extends _$NewsSearchNotifier { diff --git a/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.g.dart b/mobile-app-new/lib/ui/views/news/search/search_viewmodel.g.dart similarity index 98% rename from mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.g.dart rename to mobile-app-new/lib/ui/views/news/search/search_viewmodel.g.dart index 1429d3968..b8d809643 100644 --- a/mobile-app-new/lib/ui/views/news/news-search/news_search_viewmodel.g.dart +++ b/mobile-app-new/lib/ui/views/news/search/search_viewmodel.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'news_search_viewmodel.dart'; +part of 'search_viewmodel.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/ui/views/news/news-tag-feed/news_tag_feed_view.dart b/mobile-app-new/lib/ui/views/news/tag-feed/tag_feed_view.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/news-tag-feed/news_tag_feed_view.dart rename to mobile-app-new/lib/ui/views/news/tag-feed/tag_feed_view.dart diff --git a/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart b/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart index a9e80c46f..d30a0d638 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; -import 'package:mobile_app_new/ui/views/news/news-bookmark-feed/news_bookmark_feed_viewmodel.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/news_bottom_button.dart'; +import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/ui/views/news/widgets/bottom_button.dart'; class BookmarkButton extends ConsumerWidget { const BookmarkButton({super.key, required this.post}); @@ -14,8 +14,7 @@ class BookmarkButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final bookmarked = ref.watch( newsBookmarksProvider.select( - (bookmarks) => - bookmarks.value?.any((b) => b.id == post.id) ?? false, + (bookmarks) => bookmarks.value?.any((b) => b.id == post.id) ?? false, ), ); diff --git a/mobile-app-new/lib/ui/views/news/widgets/news_bottom_button.dart b/mobile-app-new/lib/ui/views/news/widgets/bottom_button.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/widgets/news_bottom_button.dart rename to mobile-app-new/lib/ui/views/news/widgets/bottom_button.dart diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart index e82d5fbc4..edd79910c 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart'; import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; +import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; class PostFeedList extends ConsumerStatefulWidget { const PostFeedList({ @@ -27,8 +28,6 @@ class PostFeedList extends ConsumerStatefulWidget { class _PostFeedListState extends ConsumerState { final _scrollController = ScrollController(); - bool get _hasNextPage => ref.read(_provider.notifier).hasNextPage; - NewsFeedNotifierProvider get _provider => newsFeedProvider(tagSlug: widget.tagSlug, authorId: widget.authorId); @@ -53,15 +52,17 @@ class _PostFeedListState extends ConsumerState { @override Widget build(BuildContext context) { - final postsAsync = ref.watch(_provider); + final feedAsync = ref.watch(_provider); return ColoredBox( color: FccColors.gray90, - child: postsAsync.when( + child: feedAsync.when( loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stack) => - _ErrorView(onRetry: () => ref.invalidate(_provider)), - data: (posts) => RefreshIndicator( + error: (error, stack) => ErrorRetry( + message: 'Unable to load tutorials.', + onRetry: () => ref.invalidate(_provider), + ), + data: (feed) => RefreshIndicator( onRefresh: () async => ref.invalidate(_provider), backgroundColor: FccColors.gray90, color: Colors.white, @@ -81,47 +82,50 @@ class _PostFeedListState extends ConsumerState { ], ), ), - SliverList.separated( - itemCount: posts.length + (_hasNextPage ? 1 : 0), - separatorBuilder: (_, _) => const Divider( - color: FccColors.gray80, - thickness: 1, - height: 1, - ), - itemBuilder: (context, index) => index == posts.length - ? const Padding( - padding: EdgeInsets.symmetric(vertical: 24), - child: Center(child: CircularProgressIndicator()), - ) - : PostTile( - key: ValueKey(posts[index].id), - post: posts[index], - ), - ), + _buildList(feed), ], ), ), ), ); } -} -class _ErrorView extends StatelessWidget { - const _ErrorView({required this.onRetry}); + Widget _buildList(NewsFeedState feed) { + final footer = _buildFooter(feed); + + return SliverList.separated( + itemCount: feed.posts.length + (footer == null ? 0 : 1), + separatorBuilder: (_, _) => + const Divider(color: FccColors.gray80, thickness: 1, height: 1), + itemBuilder: (context, index) => index == feed.posts.length + ? footer + : PostTile( + key: ValueKey(feed.posts[index].id), + post: feed.posts[index], + ), + ); + } - final VoidCallback onRetry; + // Null when there is nothing to append: idle, or every page is loaded. + Widget? _buildFooter(NewsFeedState feed) { + if (feed.loadMoreError != null) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: ErrorRetry( + message: 'Unable to load more tutorials.', + onRetry: () => + ref.read(_provider.notifier).fetchNextPage(isRetry: true), + ), + ); + } - @override - Widget build(BuildContext context) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Unable to load tutorials.', textAlign: TextAlign.center), - const SizedBox(height: 12), - TextButton(onPressed: onRetry, child: const Text('Retry')), - ], - ), - ); + if (feed.isLoadingMore) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 24), + child: Center(child: CircularProgressIndicator()), + ); + } + + return null; } } diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart index 94712799c..1a19b050b 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart @@ -1,50 +1,78 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:mobile_app_new/models/news/post_model.dart'; import 'package:mobile_app_new/services/news/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; +part 'post_feed_list_state.freezed.dart'; part 'post_feed_list_state.g.dart'; +@freezed +abstract class NewsFeedState with _$NewsFeedState { + const factory NewsFeedState({ + @Default([]) List posts, + @Default('') String cursor, + @Default(true) bool hasNextPage, + @Default(false) bool isLoadingMore, + // NOTE: Set when appending a page failed; the posts already loaded stay valid. + Object? loadMoreError, + }) = _NewsFeedState; +} + @riverpod class NewsFeedNotifier extends _$NewsFeedNotifier { - bool _isFetchingNext = false; - - String _endCursor = ''; - bool _hasNextPage = true; - final List _posts = []; - - bool get hasNextPage => _hasNextPage; - @override - FutureOr> build({ + FutureOr build({ String tagSlug = '', String authorId = '', }) async { - return _fetchPage(); + final page = await _fetch(''); + + return NewsFeedState( + posts: page.posts, + cursor: page.endCursor, + hasNextPage: page.hasNextPage, + ); } - Future> _fetchPage() async { + Future _fetch(String cursor) { final service = ref.read(newsApiServiceProvider); - final PostsPage page; + if (authorId.isNotEmpty) { - page = await service.getPostsByAuthor(authorId, afterCursor: _endCursor); - } else if (tagSlug.isNotEmpty) { - page = await service.getPostsByTag(tagSlug, afterCursor: _endCursor); - } else { - page = await service.getAllPosts(afterCursor: _endCursor); + return service.getPostsByAuthor(authorId, afterCursor: cursor); + } + if (tagSlug.isNotEmpty) { + return service.getPostsByTag(tagSlug, afterCursor: cursor); } + return service.getAllPosts(afterCursor: cursor); + } - _endCursor = page.endCursor; - _hasNextPage = page.hasNextPage; - _posts.addAll(page.posts); + Future fetchNextPage({bool isRetry = false}) async { + final current = state.value; + if (current == null || !current.hasNextPage || current.isLoadingMore) return; - return List.unmodifiable(_posts); - } + // A failed append waits for an explicit retry instead of refiring on scroll. + if (current.loadMoreError != null && !isRetry) return; + + state = AsyncData( + current.copyWith(isLoadingMore: true, loadMoreError: null), + ); - Future fetchNextPage() async { - if (!_hasNextPage || _isFetchingNext) return; + try { + final page = await _fetch(current.cursor); - _isFetchingNext = true; - state = await AsyncValue.guard(() => _fetchPage()); - _isFetchingNext = false; + state = AsyncData( + current.copyWith( + posts: [...current.posts, ...page.posts], + cursor: page.endCursor, + hasNextPage: page.hasNextPage, + isLoadingMore: false, + ), + ); + } catch (error) { + // Keep the loaded posts; only the append failed. + state = AsyncData( + current.copyWith(isLoadingMore: false, loadMoreError: error), + ); + } } } diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.freezed.dart b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.freezed.dart new file mode 100644 index 000000000..9b8afd86f --- /dev/null +++ b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.freezed.dart @@ -0,0 +1,287 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'post_feed_list_state.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$NewsFeedState { + + List get posts; String get cursor; bool get hasNextPage; bool get isLoadingMore; Object? get loadMoreError; +/// Create a copy of NewsFeedState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$NewsFeedStateCopyWith get copyWith => _$NewsFeedStateCopyWithImpl(this as NewsFeedState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is NewsFeedState&&const DeepCollectionEquality().equals(other.posts, posts)&&(identical(other.cursor, cursor) || other.cursor == cursor)&&(identical(other.hasNextPage, hasNextPage) || other.hasNextPage == hasNextPage)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&const DeepCollectionEquality().equals(other.loadMoreError, loadMoreError)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(posts),cursor,hasNextPage,isLoadingMore,const DeepCollectionEquality().hash(loadMoreError)); + +@override +String toString() { + return 'NewsFeedState(posts: $posts, cursor: $cursor, hasNextPage: $hasNextPage, isLoadingMore: $isLoadingMore, loadMoreError: $loadMoreError)'; +} + + +} + +/// @nodoc +abstract mixin class $NewsFeedStateCopyWith<$Res> { + factory $NewsFeedStateCopyWith(NewsFeedState value, $Res Function(NewsFeedState) _then) = _$NewsFeedStateCopyWithImpl; +@useResult +$Res call({ + List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError +}); + + + + +} +/// @nodoc +class _$NewsFeedStateCopyWithImpl<$Res> + implements $NewsFeedStateCopyWith<$Res> { + _$NewsFeedStateCopyWithImpl(this._self, this._then); + + final NewsFeedState _self; + final $Res Function(NewsFeedState) _then; + +/// Create a copy of NewsFeedState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? posts = null,Object? cursor = null,Object? hasNextPage = null,Object? isLoadingMore = null,Object? loadMoreError = freezed,}) { + return _then(_self.copyWith( +posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable +as List,cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String,hasNextPage: null == hasNextPage ? _self.hasNextPage : hasNextPage // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable +as bool,loadMoreError: freezed == loadMoreError ? _self.loadMoreError : loadMoreError , + )); +} + +} + + +/// Adds pattern-matching-related methods to [NewsFeedState]. +extension NewsFeedStatePatterns on NewsFeedState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _NewsFeedState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _NewsFeedState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _NewsFeedState value) $default,){ +final _that = this; +switch (_that) { +case _NewsFeedState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _NewsFeedState value)? $default,){ +final _that = this; +switch (_that) { +case _NewsFeedState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _NewsFeedState() when $default != null: +return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError) $default,) {final _that = this; +switch (_that) { +case _NewsFeedState(): +return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,) {final _that = this; +switch (_that) { +case _NewsFeedState() when $default != null: +return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _NewsFeedState implements NewsFeedState { + const _NewsFeedState({final List posts = const [], this.cursor = '', this.hasNextPage = true, this.isLoadingMore = false, this.loadMoreError}): _posts = posts; + + + final List _posts; +@override@JsonKey() List get posts { + if (_posts is EqualUnmodifiableListView) return _posts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_posts); +} + +@override@JsonKey() final String cursor; +@override@JsonKey() final bool hasNextPage; +@override@JsonKey() final bool isLoadingMore; +@override final Object? loadMoreError; + +/// Create a copy of NewsFeedState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$NewsFeedStateCopyWith<_NewsFeedState> get copyWith => __$NewsFeedStateCopyWithImpl<_NewsFeedState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _NewsFeedState&&const DeepCollectionEquality().equals(other._posts, _posts)&&(identical(other.cursor, cursor) || other.cursor == cursor)&&(identical(other.hasNextPage, hasNextPage) || other.hasNextPage == hasNextPage)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&const DeepCollectionEquality().equals(other.loadMoreError, loadMoreError)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_posts),cursor,hasNextPage,isLoadingMore,const DeepCollectionEquality().hash(loadMoreError)); + +@override +String toString() { + return 'NewsFeedState(posts: $posts, cursor: $cursor, hasNextPage: $hasNextPage, isLoadingMore: $isLoadingMore, loadMoreError: $loadMoreError)'; +} + + +} + +/// @nodoc +abstract mixin class _$NewsFeedStateCopyWith<$Res> implements $NewsFeedStateCopyWith<$Res> { + factory _$NewsFeedStateCopyWith(_NewsFeedState value, $Res Function(_NewsFeedState) _then) = __$NewsFeedStateCopyWithImpl; +@override @useResult +$Res call({ + List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError +}); + + + + +} +/// @nodoc +class __$NewsFeedStateCopyWithImpl<$Res> + implements _$NewsFeedStateCopyWith<$Res> { + __$NewsFeedStateCopyWithImpl(this._self, this._then); + + final _NewsFeedState _self; + final $Res Function(_NewsFeedState) _then; + +/// Create a copy of NewsFeedState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? posts = null,Object? cursor = null,Object? hasNextPage = null,Object? isLoadingMore = null,Object? loadMoreError = freezed,}) { + return _then(_NewsFeedState( +posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable +as List,cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String,hasNextPage: null == hasNextPage ? _self.hasNextPage : hasNextPage // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable +as bool,loadMoreError: freezed == loadMoreError ? _self.loadMoreError : loadMoreError , + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart index 17e033539..f166eb551 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart +++ b/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart @@ -13,7 +13,7 @@ part of 'post_feed_list_state.dart'; final newsFeedProvider = NewsFeedNotifierFamily._(); final class NewsFeedNotifierProvider - extends $AsyncNotifierProvider> { + extends $AsyncNotifierProvider { NewsFeedNotifierProvider._({ required NewsFeedNotifierFamily super.from, required ({String tagSlug, String authorId}) super.argument, @@ -50,15 +50,15 @@ final class NewsFeedNotifierProvider } } -String _$newsFeedNotifierHash() => r'7f00ed34d515b8035020b3a8f84c85566c4c6490'; +String _$newsFeedNotifierHash() => r'f67d743e7fd7f603374d990de1f4dd336b91a6ff'; final class NewsFeedNotifierFamily extends $Family with $ClassFamilyOverride< NewsFeedNotifier, - AsyncValue>, - List, - FutureOr>, + AsyncValue, + NewsFeedState, + FutureOr, ({String tagSlug, String authorId}) > { NewsFeedNotifierFamily._() @@ -80,21 +80,21 @@ final class NewsFeedNotifierFamily extends $Family String toString() => r'newsFeedProvider'; } -abstract class _$NewsFeedNotifier extends $AsyncNotifier> { +abstract class _$NewsFeedNotifier extends $AsyncNotifier { late final _$args = ref.$arg as ({String tagSlug, String authorId}); String get tagSlug => _$args.tagSlug; String get authorId => _$args.authorId; - FutureOr> build({String tagSlug = '', String authorId = ''}); + FutureOr build({String tagSlug = '', String authorId = ''}); @$mustCallSuper @override WhenComplete runBuild() { - final ref = this.ref as $Ref>, List>; + final ref = this.ref as $Ref, NewsFeedState>; final element = ref.element as $ClassProviderElement< - AnyNotifier>, List>, - AsyncValue>, + AnyNotifier, NewsFeedState>, + AsyncValue, Object?, Object? >; diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index cadf870f0..0e222f433 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.49.2" + analysis_server_plugin: + dependency: transitive + description: + name: analysis_server_plugin + sha256: "3960b28ee740004df39f85d5ebfc91785f7a90e51fd7c9a185e86a36b2f581b4" + url: "https://pub.dev" + source: hosted + version: "0.3.14" analyzer: dependency: transitive description: @@ -73,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.3" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "0057a98d64d7bb872b0c87dff6e73d2c2d80c77156e7a03f127a26f8aa240649" + url: "https://pub.dev" + source: hosted + version: "0.14.8" archive: dependency: transitive description: @@ -1007,6 +1023,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: "166f29492228dc471b6fe294092560ccd5545ed15c9fc155622455b58b2451a9" + url: "https://pub.dev" + source: hosted + version: "3.1.4" rxdart: dependency: transitive description: @@ -1492,6 +1516,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488" + url: "https://pub.dev" + source: hosted + version: "2.2.4" youtube_player_iframe: dependency: "direct main" description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 4264b0efc..454de89ba 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -77,6 +77,7 @@ dev_dependencies: freezed: ^3.2.6-dev.1 json_serializable: ^6.14.1 go_router_builder: ^4.4.0 + riverpod_lint: 3.1.4 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 396f085eb433415ca426ab428182b075db4b6469 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 6 Sep 2026 13:04:09 +0530 Subject: [PATCH 26/52] feature level folder structure --- .../integration_test/news/bookmark_test.dart | 8 +++---- .../lib/{ => learn/ui}/home_page.dart | 2 +- .../{ => news}/constants/radio_articles.dart | 0 .../news => news/models}/author_model.dart | 0 .../models}/author_model.freezed.dart | 0 .../news => news/models}/author_model.g.dart | 0 .../models}/bookmarked_post_model.dart | 6 ++++- .../bookmarked_post_model.freezed.dart | 16 +++++++++---- .../news/models/bookmarked_post_model.g.dart | 23 +++++++++++++++++++ .../news => news/models}/post_model.dart | 0 .../models}/post_model.freezed.dart | 0 .../news => news/models}/post_model.g.dart | 0 .../models}/search_post_model.dart | 0 .../models}/search_post_model.freezed.dart | 0 .../models}/search_post_model.g.dart | 0 .../repositories}/api_repository.dart | 0 .../repositories}/api_repository.g.dart | 0 .../repositories}/bookmark_repository.dart | 0 .../repositories}/bookmark_repository.g.dart | 0 .../repositories}/search_repository.dart | 0 .../repositories}/search_repository.g.dart | 0 .../news => news/services}/api_service.dart | 6 ++--- .../news => news/services}/api_service.g.dart | 0 .../services}/bookmark_service.dart | 4 ++-- .../services}/bookmark_service.g.dart | 0 .../services}/search_service.dart | 6 ++--- .../services}/search_service.g.dart | 0 .../ui/author_feed}/author_feed_view.dart | 8 +++---- .../author_feed}/author_feed_viewmodel.dart | 4 ++-- .../author_feed}/author_feed_viewmodel.g.dart | 0 .../ui/bookmark_feed}/bookmark_feed_view.dart | 6 ++--- .../bookmark_feed_viewmodel.dart | 4 ++-- .../bookmark_feed_viewmodel.g.dart | 0 .../ui/bookmark_post}/bookmark_post_view.dart | 8 +++---- .../news => news/ui}/feed/feed_view.dart | 2 +- .../news => news/ui}/image/image_view.dart | 0 .../views/news => news/ui}/news_shell.dart | 2 +- .../news => news/ui}/post/post_view.dart | 16 ++++++------- .../news => news/ui}/post/post_viewmodel.dart | 4 ++-- .../ui}/post/post_viewmodel.g.dart | 0 .../news => news/ui}/search/search_view.dart | 6 ++--- .../ui}/search/search_viewmodel.dart | 4 ++-- .../ui}/search/search_viewmodel.g.dart | 0 .../ui/tag_feed}/tag_feed_view.dart | 2 +- .../ui}/widgets/back_to_top_button.dart | 0 .../ui}/widgets/bookmark_button.dart | 8 +++---- .../ui}/widgets/bottom_button.dart | 0 .../post_feed_list}/post_feed_list.dart | 6 ++--- .../post_feed_list}/post_feed_list_state.dart | 4 ++-- .../post_feed_list_state.freezed.dart | 0 .../post_feed_list_state.g.dart | 2 +- .../news => news/ui}/widgets/post_tile.dart | 4 ++-- .../ui}/widgets/search_result_tile.dart | 2 +- .../news => news/ui}/widgets/tag_button.dart | 0 mobile-app-new/lib/routing/learn.dart | 2 +- mobile-app-new/lib/routing/news.dart | 22 +++++++++--------- .../{ui/core => widgets}/drawer/drawer.dart | 2 +- .../core => widgets}/drawer/drawer_tile.dart | 0 .../{ui/core => }/widgets/error_retry.dart | 0 .../html_handler/html_handler.dart | 0 60 files changed, 111 insertions(+), 78 deletions(-) rename mobile-app-new/lib/{ => learn/ui}/home_page.dart (98%) rename mobile-app-new/lib/{ => news}/constants/radio_articles.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/author_model.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/author_model.freezed.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/author_model.g.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/bookmarked_post_model.dart (71%) rename mobile-app-new/lib/{models/news => news/models}/bookmarked_post_model.freezed.dart (95%) create mode 100644 mobile-app-new/lib/news/models/bookmarked_post_model.g.dart rename mobile-app-new/lib/{models/news => news/models}/post_model.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/post_model.freezed.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/post_model.g.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/search_post_model.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/search_post_model.freezed.dart (100%) rename mobile-app-new/lib/{models/news => news/models}/search_post_model.g.dart (100%) rename mobile-app-new/lib/{repositories/news => news/repositories}/api_repository.dart (100%) rename mobile-app-new/lib/{repositories/news => news/repositories}/api_repository.g.dart (100%) rename mobile-app-new/lib/{repositories/news => news/repositories}/bookmark_repository.dart (100%) rename mobile-app-new/lib/{repositories/news => news/repositories}/bookmark_repository.g.dart (100%) rename mobile-app-new/lib/{repositories/news => news/repositories}/search_repository.dart (100%) rename mobile-app-new/lib/{repositories/news => news/repositories}/search_repository.g.dart (100%) rename mobile-app-new/lib/{services/news => news/services}/api_service.dart (90%) rename mobile-app-new/lib/{services/news => news/services}/api_service.g.dart (100%) rename mobile-app-new/lib/{services/news => news/services}/bookmark_service.dart (93%) rename mobile-app-new/lib/{services/news => news/services}/bookmark_service.g.dart (100%) rename mobile-app-new/lib/{services/news => news/services}/search_service.dart (81%) rename mobile-app-new/lib/{services/news => news/services}/search_service.g.dart (100%) rename mobile-app-new/lib/{ui/views/news/author-feed => news/ui/author_feed}/author_feed_view.dart (91%) rename mobile-app-new/lib/{ui/views/news/author-feed => news/ui/author_feed}/author_feed_viewmodel.dart (71%) rename mobile-app-new/lib/{ui/views/news/author-feed => news/ui/author_feed}/author_feed_viewmodel.g.dart (100%) rename mobile-app-new/lib/{ui/views/news/bookmark-feed => news/ui/bookmark_feed}/bookmark_feed_view.dart (91%) rename mobile-app-new/lib/{ui/views/news/bookmark-feed => news/ui/bookmark_feed}/bookmark_feed_viewmodel.dart (83%) rename mobile-app-new/lib/{ui/views/news/bookmark-feed => news/ui/bookmark_feed}/bookmark_feed_viewmodel.g.dart (100%) rename mobile-app-new/lib/{ui/views/news/bookmark-post => news/ui/bookmark_post}/bookmark_post_view.dart (91%) rename mobile-app-new/lib/{ui/views/news => news/ui}/feed/feed_view.dart (68%) rename mobile-app-new/lib/{ui/views/news => news/ui}/image/image_view.dart (100%) rename mobile-app-new/lib/{ui/views/news => news/ui}/news_shell.dart (96%) rename mobile-app-new/lib/{ui/views/news => news/ui}/post/post_view.dart (93%) rename mobile-app-new/lib/{ui/views/news => news/ui}/post/post_viewmodel.dart (63%) rename mobile-app-new/lib/{ui/views/news => news/ui}/post/post_viewmodel.g.dart (100%) rename mobile-app-new/lib/{ui/views/news => news/ui}/search/search_view.dart (92%) rename mobile-app-new/lib/{ui/views/news => news/ui}/search/search_viewmodel.dart (85%) rename mobile-app-new/lib/{ui/views/news => news/ui}/search/search_viewmodel.g.dart (100%) rename mobile-app-new/lib/{ui/views/news/tag-feed => news/ui/tag_feed}/tag_feed_view.dart (85%) rename mobile-app-new/lib/{ui/views/news => news/ui}/widgets/back_to_top_button.dart (100%) rename mobile-app-new/lib/{ui/views/news => news/ui}/widgets/bookmark_button.dart (73%) rename mobile-app-new/lib/{ui/views/news => news/ui}/widgets/bottom_button.dart (100%) rename mobile-app-new/lib/{ui/views/news/widgets/post-feed-list => news/ui/widgets/post_feed_list}/post_feed_list.dart (93%) rename mobile-app-new/lib/{ui/views/news/widgets/post-feed-list => news/ui/widgets/post_feed_list}/post_feed_list_state.dart (94%) rename mobile-app-new/lib/{ui/views/news/widgets/post-feed-list => news/ui/widgets/post_feed_list}/post_feed_list_state.freezed.dart (100%) rename mobile-app-new/lib/{ui/views/news/widgets/post-feed-list => news/ui/widgets/post_feed_list}/post_feed_list_state.g.dart (97%) rename mobile-app-new/lib/{ui/views/news => news/ui}/widgets/post_tile.dart (97%) rename mobile-app-new/lib/{ui/views/news => news/ui}/widgets/search_result_tile.dart (98%) rename mobile-app-new/lib/{ui/views/news => news/ui}/widgets/tag_button.dart (100%) rename mobile-app-new/lib/{ui/core => widgets}/drawer/drawer.dart (92%) rename mobile-app-new/lib/{ui/core => widgets}/drawer/drawer_tile.dart (100%) rename mobile-app-new/lib/{ui/core => }/widgets/error_retry.dart (100%) rename mobile-app-new/lib/{ui/core => widgets}/html_handler/html_handler.dart (100%) diff --git a/mobile-app-new/integration_test/news/bookmark_test.dart b/mobile-app-new/integration_test/news/bookmark_test.dart index aa5460410..360063fdc 100644 --- a/mobile-app-new/integration_test/news/bookmark_test.dart +++ b/mobile-app-new/integration_test/news/bookmark_test.dart @@ -5,10 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mobile_app_new/main.dart' as app; -import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/post/post_view.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/bookmark_button.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; +import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_view.dart'; +import 'package:mobile_app_new/news/ui/post/post_view.dart'; +import 'package:mobile_app_new/news/ui/widgets/bookmark_button.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_tile.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; diff --git a/mobile-app-new/lib/home_page.dart b/mobile-app-new/lib/learn/ui/home_page.dart similarity index 98% rename from mobile-app-new/lib/home_page.dart rename to mobile-app-new/lib/learn/ui/home_page.dart index 83136b250..5cff6668f 100644 --- a/mobile-app-new/lib/home_page.dart +++ b/mobile-app-new/lib/learn/ui/home_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); diff --git a/mobile-app-new/lib/constants/radio_articles.dart b/mobile-app-new/lib/news/constants/radio_articles.dart similarity index 100% rename from mobile-app-new/lib/constants/radio_articles.dart rename to mobile-app-new/lib/news/constants/radio_articles.dart diff --git a/mobile-app-new/lib/models/news/author_model.dart b/mobile-app-new/lib/news/models/author_model.dart similarity index 100% rename from mobile-app-new/lib/models/news/author_model.dart rename to mobile-app-new/lib/news/models/author_model.dart diff --git a/mobile-app-new/lib/models/news/author_model.freezed.dart b/mobile-app-new/lib/news/models/author_model.freezed.dart similarity index 100% rename from mobile-app-new/lib/models/news/author_model.freezed.dart rename to mobile-app-new/lib/news/models/author_model.freezed.dart diff --git a/mobile-app-new/lib/models/news/author_model.g.dart b/mobile-app-new/lib/news/models/author_model.g.dart similarity index 100% rename from mobile-app-new/lib/models/news/author_model.g.dart rename to mobile-app-new/lib/news/models/author_model.g.dart diff --git a/mobile-app-new/lib/models/news/bookmarked_post_model.dart b/mobile-app-new/lib/news/models/bookmarked_post_model.dart similarity index 71% rename from mobile-app-new/lib/models/news/bookmarked_post_model.dart rename to mobile-app-new/lib/news/models/bookmarked_post_model.dart index 040eed627..1e4a4ef96 100644 --- a/mobile-app-new/lib/models/news/bookmarked_post_model.dart +++ b/mobile-app-new/lib/news/models/bookmarked_post_model.dart @@ -1,7 +1,8 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; part 'bookmarked_post_model.freezed.dart'; +part 'bookmarked_post_model.g.dart'; @freezed abstract class BookmarkedPost with _$BookmarkedPost { @@ -18,4 +19,7 @@ abstract class BookmarkedPost with _$BookmarkedPost { authorName: post.author.name, text: post.content.html, ); + + factory BookmarkedPost.fromJson(Map json) => + _$BookmarkedPostFromJson(json); } diff --git a/mobile-app-new/lib/models/news/bookmarked_post_model.freezed.dart b/mobile-app-new/lib/news/models/bookmarked_post_model.freezed.dart similarity index 95% rename from mobile-app-new/lib/models/news/bookmarked_post_model.freezed.dart rename to mobile-app-new/lib/news/models/bookmarked_post_model.freezed.dart index 777918dda..6ecd21209 100644 --- a/mobile-app-new/lib/models/news/bookmarked_post_model.freezed.dart +++ b/mobile-app-new/lib/news/models/bookmarked_post_model.freezed.dart @@ -11,6 +11,7 @@ part of 'bookmarked_post_model.dart'; // dart format off T _$identity(T value) => value; + /// @nodoc mixin _$BookmarkedPost { @@ -21,6 +22,8 @@ mixin _$BookmarkedPost { @pragma('vm:prefer-inline') $BookmarkedPostCopyWith get copyWith => _$BookmarkedPostCopyWithImpl(this as BookmarkedPost, _$identity); + /// Serializes this BookmarkedPost to a JSON map. + Map toJson(); @override @@ -28,7 +31,7 @@ bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType&&other is BookmarkedPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.text, text) || other.text == text)); } - +@JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType,id,title,authorName,text); @@ -206,11 +209,11 @@ return $default(_that.id,_that.title,_that.authorName,_that.text);case _: } /// @nodoc - +@JsonSerializable() class _BookmarkedPost implements BookmarkedPost { const _BookmarkedPost({required this.id, required this.title, required this.authorName, required this.text}); - + factory _BookmarkedPost.fromJson(Map json) => _$BookmarkedPostFromJson(json); @override final String id; @override final String title; @@ -223,14 +226,17 @@ class _BookmarkedPost implements BookmarkedPost { @pragma('vm:prefer-inline') _$BookmarkedPostCopyWith<_BookmarkedPost> get copyWith => __$BookmarkedPostCopyWithImpl<_BookmarkedPost>(this, _$identity); - +@override +Map toJson() { + return _$BookmarkedPostToJson(this, ); +} @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType&&other is _BookmarkedPost&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.authorName, authorName) || other.authorName == authorName)&&(identical(other.text, text) || other.text == text)); } - +@JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType,id,title,authorName,text); diff --git a/mobile-app-new/lib/news/models/bookmarked_post_model.g.dart b/mobile-app-new/lib/news/models/bookmarked_post_model.g.dart new file mode 100644 index 000000000..b82eb8d03 --- /dev/null +++ b/mobile-app-new/lib/news/models/bookmarked_post_model.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'bookmarked_post_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_BookmarkedPost _$BookmarkedPostFromJson(Map json) => + _BookmarkedPost( + id: json['id'] as String, + title: json['title'] as String, + authorName: json['authorName'] as String, + text: json['text'] as String, + ); + +Map _$BookmarkedPostToJson(_BookmarkedPost instance) => + { + 'id': instance.id, + 'title': instance.title, + 'authorName': instance.authorName, + 'text': instance.text, + }; diff --git a/mobile-app-new/lib/models/news/post_model.dart b/mobile-app-new/lib/news/models/post_model.dart similarity index 100% rename from mobile-app-new/lib/models/news/post_model.dart rename to mobile-app-new/lib/news/models/post_model.dart diff --git a/mobile-app-new/lib/models/news/post_model.freezed.dart b/mobile-app-new/lib/news/models/post_model.freezed.dart similarity index 100% rename from mobile-app-new/lib/models/news/post_model.freezed.dart rename to mobile-app-new/lib/news/models/post_model.freezed.dart diff --git a/mobile-app-new/lib/models/news/post_model.g.dart b/mobile-app-new/lib/news/models/post_model.g.dart similarity index 100% rename from mobile-app-new/lib/models/news/post_model.g.dart rename to mobile-app-new/lib/news/models/post_model.g.dart diff --git a/mobile-app-new/lib/models/news/search_post_model.dart b/mobile-app-new/lib/news/models/search_post_model.dart similarity index 100% rename from mobile-app-new/lib/models/news/search_post_model.dart rename to mobile-app-new/lib/news/models/search_post_model.dart diff --git a/mobile-app-new/lib/models/news/search_post_model.freezed.dart b/mobile-app-new/lib/news/models/search_post_model.freezed.dart similarity index 100% rename from mobile-app-new/lib/models/news/search_post_model.freezed.dart rename to mobile-app-new/lib/news/models/search_post_model.freezed.dart diff --git a/mobile-app-new/lib/models/news/search_post_model.g.dart b/mobile-app-new/lib/news/models/search_post_model.g.dart similarity index 100% rename from mobile-app-new/lib/models/news/search_post_model.g.dart rename to mobile-app-new/lib/news/models/search_post_model.g.dart diff --git a/mobile-app-new/lib/repositories/news/api_repository.dart b/mobile-app-new/lib/news/repositories/api_repository.dart similarity index 100% rename from mobile-app-new/lib/repositories/news/api_repository.dart rename to mobile-app-new/lib/news/repositories/api_repository.dart diff --git a/mobile-app-new/lib/repositories/news/api_repository.g.dart b/mobile-app-new/lib/news/repositories/api_repository.g.dart similarity index 100% rename from mobile-app-new/lib/repositories/news/api_repository.g.dart rename to mobile-app-new/lib/news/repositories/api_repository.g.dart diff --git a/mobile-app-new/lib/repositories/news/bookmark_repository.dart b/mobile-app-new/lib/news/repositories/bookmark_repository.dart similarity index 100% rename from mobile-app-new/lib/repositories/news/bookmark_repository.dart rename to mobile-app-new/lib/news/repositories/bookmark_repository.dart diff --git a/mobile-app-new/lib/repositories/news/bookmark_repository.g.dart b/mobile-app-new/lib/news/repositories/bookmark_repository.g.dart similarity index 100% rename from mobile-app-new/lib/repositories/news/bookmark_repository.g.dart rename to mobile-app-new/lib/news/repositories/bookmark_repository.g.dart diff --git a/mobile-app-new/lib/repositories/news/search_repository.dart b/mobile-app-new/lib/news/repositories/search_repository.dart similarity index 100% rename from mobile-app-new/lib/repositories/news/search_repository.dart rename to mobile-app-new/lib/news/repositories/search_repository.dart diff --git a/mobile-app-new/lib/repositories/news/search_repository.g.dart b/mobile-app-new/lib/news/repositories/search_repository.g.dart similarity index 100% rename from mobile-app-new/lib/repositories/news/search_repository.g.dart rename to mobile-app-new/lib/news/repositories/search_repository.g.dart diff --git a/mobile-app-new/lib/services/news/api_service.dart b/mobile-app-new/lib/news/services/api_service.dart similarity index 90% rename from mobile-app-new/lib/services/news/api_service.dart rename to mobile-app-new/lib/news/services/api_service.dart index ef4a6c146..5b231f867 100644 --- a/mobile-app-new/lib/services/news/api_service.dart +++ b/mobile-app-new/lib/news/services/api_service.dart @@ -1,6 +1,6 @@ -import 'package:mobile_app_new/models/news/author_model.dart'; -import 'package:mobile_app_new/models/news/post_model.dart'; -import 'package:mobile_app_new/repositories/news/api_repository.dart'; +import 'package:mobile_app_new/news/models/author_model.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; +import 'package:mobile_app_new/news/repositories/api_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'api_service.g.dart'; diff --git a/mobile-app-new/lib/services/news/api_service.g.dart b/mobile-app-new/lib/news/services/api_service.g.dart similarity index 100% rename from mobile-app-new/lib/services/news/api_service.g.dart rename to mobile-app-new/lib/news/services/api_service.g.dart diff --git a/mobile-app-new/lib/services/news/bookmark_service.dart b/mobile-app-new/lib/news/services/bookmark_service.dart similarity index 93% rename from mobile-app-new/lib/services/news/bookmark_service.dart rename to mobile-app-new/lib/news/services/bookmark_service.dart index f33e1efcf..a7a4ba000 100644 --- a/mobile-app-new/lib/services/news/bookmark_service.dart +++ b/mobile-app-new/lib/news/services/bookmark_service.dart @@ -1,5 +1,5 @@ -import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; -import 'package:mobile_app_new/repositories/news/bookmark_repository.dart'; +import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; +import 'package:mobile_app_new/news/repositories/bookmark_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'bookmark_service.g.dart'; diff --git a/mobile-app-new/lib/services/news/bookmark_service.g.dart b/mobile-app-new/lib/news/services/bookmark_service.g.dart similarity index 100% rename from mobile-app-new/lib/services/news/bookmark_service.g.dart rename to mobile-app-new/lib/news/services/bookmark_service.g.dart diff --git a/mobile-app-new/lib/services/news/search_service.dart b/mobile-app-new/lib/news/services/search_service.dart similarity index 81% rename from mobile-app-new/lib/services/news/search_service.dart rename to mobile-app-new/lib/news/services/search_service.dart index 573e38e26..538a9cba4 100644 --- a/mobile-app-new/lib/services/news/search_service.dart +++ b/mobile-app-new/lib/news/services/search_service.dart @@ -1,9 +1,9 @@ import 'dart:io'; import 'package:algolia_helper_flutter/algolia_helper_flutter.dart'; -import 'package:mobile_app_new/constants/radio_articles.dart'; -import 'package:mobile_app_new/models/news/search_post_model.dart'; -import 'package:mobile_app_new/repositories/news/search_repository.dart'; +import 'package:mobile_app_new/news/constants/radio_articles.dart'; +import 'package:mobile_app_new/news/models/search_post_model.dart'; +import 'package:mobile_app_new/news/repositories/search_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'search_service.g.dart'; diff --git a/mobile-app-new/lib/services/news/search_service.g.dart b/mobile-app-new/lib/news/services/search_service.g.dart similarity index 100% rename from mobile-app-new/lib/services/news/search_service.g.dart rename to mobile-app-new/lib/news/services/search_service.g.dart diff --git a/mobile-app-new/lib/ui/views/news/author-feed/author_feed_view.dart b/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart similarity index 91% rename from mobile-app-new/lib/ui/views/news/author-feed/author_feed_view.dart rename to mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart index e528bd72e..59ca6587e 100644 --- a/mobile-app-new/lib/ui/views/news/author-feed/author_feed_view.dart +++ b/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/models/news/author_model.dart'; -import 'package:mobile_app_new/ui/views/news/author-feed/author_feed_viewmodel.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list.dart'; -import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; +import 'package:mobile_app_new/news/models/author_model.dart'; +import 'package:mobile_app_new/news/ui/author_feed/author_feed_viewmodel.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; class NewsAuthorFeedView extends ConsumerWidget { const NewsAuthorFeedView({super.key, required this.username, this.author}); diff --git a/mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.dart b/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.dart similarity index 71% rename from mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.dart rename to mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.dart index 7cdfa41b4..4bb5ab281 100644 --- a/mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.dart +++ b/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.dart @@ -1,5 +1,5 @@ -import 'package:mobile_app_new/models/news/author_model.dart'; -import 'package:mobile_app_new/services/news/api_service.dart'; +import 'package:mobile_app_new/news/models/author_model.dart'; +import 'package:mobile_app_new/news/services/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'author_feed_viewmodel.g.dart'; diff --git a/mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.g.dart b/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.g.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/author-feed/author_feed_viewmodel.g.dart rename to mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.g.dart diff --git a/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_view.dart b/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_view.dart similarity index 91% rename from mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_view.dart rename to mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_view.dart index dff8ab962..3b14af486 100644 --- a/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_view.dart +++ b/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_view.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; +import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart'; -import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; +import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; class NewsBookmarkFeedView extends ConsumerWidget { const NewsBookmarkFeedView({super.key}); diff --git a/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart b/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart similarity index 83% rename from mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart rename to mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart index 7338d4a41..359d282d0 100644 --- a/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart +++ b/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart @@ -1,5 +1,5 @@ -import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; -import 'package:mobile_app_new/services/news/bookmark_service.dart'; +import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; +import 'package:mobile_app_new/news/services/bookmark_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'bookmark_feed_viewmodel.g.dart'; diff --git a/mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.g.dart b/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.g.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.g.dart rename to mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.g.dart diff --git a/mobile-app-new/lib/ui/views/news/bookmark-post/bookmark_post_view.dart b/mobile-app-new/lib/news/ui/bookmark_post/bookmark_post_view.dart similarity index 91% rename from mobile-app-new/lib/ui/views/news/bookmark-post/bookmark_post_view.dart rename to mobile-app-new/lib/news/ui/bookmark_post/bookmark_post_view.dart index c4c814c67..0399e1d12 100644 --- a/mobile-app-new/lib/ui/views/news/bookmark-post/bookmark_post_view.dart +++ b/mobile-app-new/lib/news/ui/bookmark_post/bookmark_post_view.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; -import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/back_to_top_button.dart'; -import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; +import 'package:mobile_app_new/news/ui/widgets/back_to_top_button.dart'; +import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart'; // Reads the HTML stored at bookmark time, so it works with no network. class NewsBookmarkPostView extends ConsumerStatefulWidget { diff --git a/mobile-app-new/lib/ui/views/news/feed/feed_view.dart b/mobile-app-new/lib/news/ui/feed/feed_view.dart similarity index 68% rename from mobile-app-new/lib/ui/views/news/feed/feed_view.dart rename to mobile-app-new/lib/news/ui/feed/feed_view.dart index d966f8250..d62625be9 100644 --- a/mobile-app-new/lib/ui/views/news/feed/feed_view.dart +++ b/mobile-app-new/lib/news/ui/feed/feed_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list.dart'; class NewsFeedView extends StatelessWidget { const NewsFeedView({super.key}); diff --git a/mobile-app-new/lib/ui/views/news/image/image_view.dart b/mobile-app-new/lib/news/ui/image/image_view.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/image/image_view.dart rename to mobile-app-new/lib/news/ui/image/image_view.dart diff --git a/mobile-app-new/lib/ui/views/news/news_shell.dart b/mobile-app-new/lib/news/ui/news_shell.dart similarity index 96% rename from mobile-app-new/lib/ui/views/news/news_shell.dart rename to mobile-app-new/lib/news/ui/news_shell.dart index f358da941..c1d086f25 100644 --- a/mobile-app-new/lib/ui/views/news/news_shell.dart +++ b/mobile-app-new/lib/news/ui/news_shell.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/ui/core/drawer/drawer.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; typedef _Tab = ({String title, String path, IconData icon}); diff --git a/mobile-app-new/lib/ui/views/news/post/post_view.dart b/mobile-app-new/lib/news/ui/post/post_view.dart similarity index 93% rename from mobile-app-new/lib/ui/views/news/post/post_view.dart rename to mobile-app-new/lib/news/ui/post/post_view.dart index f41c83b36..af67c3da2 100644 --- a/mobile-app-new/lib/ui/views/news/post/post_view.dart +++ b/mobile-app-new/lib/news/ui/post/post_view.dart @@ -5,16 +5,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/ui/core/html_handler/html_handler.dart'; -import 'package:mobile_app_new/ui/views/news/post/post_viewmodel.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/back_to_top_button.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/bookmark_button.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/bottom_button.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; +import 'package:mobile_app_new/news/ui/post/post_viewmodel.dart'; +import 'package:mobile_app_new/news/ui/widgets/back_to_top_button.dart'; +import 'package:mobile_app_new/news/ui/widgets/bookmark_button.dart'; +import 'package:mobile_app_new/news/ui/widgets/bottom_button.dart'; +import 'package:mobile_app_new/news/ui/widgets/tag_button.dart'; import 'package:share_plus/share_plus.dart'; -import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; class NewsPostHeader extends StatelessWidget { const NewsPostHeader({super.key, required this.post}); diff --git a/mobile-app-new/lib/ui/views/news/post/post_viewmodel.dart b/mobile-app-new/lib/news/ui/post/post_viewmodel.dart similarity index 63% rename from mobile-app-new/lib/ui/views/news/post/post_viewmodel.dart rename to mobile-app-new/lib/news/ui/post/post_viewmodel.dart index 74ee1de28..7a5f7bc78 100644 --- a/mobile-app-new/lib/ui/views/news/post/post_viewmodel.dart +++ b/mobile-app-new/lib/news/ui/post/post_viewmodel.dart @@ -1,5 +1,5 @@ -import 'package:mobile_app_new/models/news/post_model.dart'; -import 'package:mobile_app_new/services/news/api_service.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; +import 'package:mobile_app_new/news/services/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'post_viewmodel.g.dart'; diff --git a/mobile-app-new/lib/ui/views/news/post/post_viewmodel.g.dart b/mobile-app-new/lib/news/ui/post/post_viewmodel.g.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/post/post_viewmodel.g.dart rename to mobile-app-new/lib/news/ui/post/post_viewmodel.g.dart diff --git a/mobile-app-new/lib/ui/views/news/search/search_view.dart b/mobile-app-new/lib/news/ui/search/search_view.dart similarity index 92% rename from mobile-app-new/lib/ui/views/news/search/search_view.dart rename to mobile-app-new/lib/news/ui/search/search_view.dart index 62b936cf4..0a2140bd9 100644 --- a/mobile-app-new/lib/ui/views/news/search/search_view.dart +++ b/mobile-app-new/lib/news/ui/search/search_view.dart @@ -3,9 +3,9 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/models/news/search_post_model.dart'; -import 'package:mobile_app_new/ui/views/news/search/search_viewmodel.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/search_result_tile.dart'; +import 'package:mobile_app_new/news/models/search_post_model.dart'; +import 'package:mobile_app_new/news/ui/search/search_viewmodel.dart'; +import 'package:mobile_app_new/news/ui/widgets/search_result_tile.dart'; class NewsSearchView extends ConsumerStatefulWidget { const NewsSearchView({super.key}); diff --git a/mobile-app-new/lib/ui/views/news/search/search_viewmodel.dart b/mobile-app-new/lib/news/ui/search/search_viewmodel.dart similarity index 85% rename from mobile-app-new/lib/ui/views/news/search/search_viewmodel.dart rename to mobile-app-new/lib/news/ui/search/search_viewmodel.dart index 673227107..1151545f3 100644 --- a/mobile-app-new/lib/ui/views/news/search/search_viewmodel.dart +++ b/mobile-app-new/lib/news/ui/search/search_viewmodel.dart @@ -1,5 +1,5 @@ -import 'package:mobile_app_new/models/news/search_post_model.dart'; -import 'package:mobile_app_new/services/news/search_service.dart'; +import 'package:mobile_app_new/news/models/search_post_model.dart'; +import 'package:mobile_app_new/news/services/search_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'search_viewmodel.g.dart'; diff --git a/mobile-app-new/lib/ui/views/news/search/search_viewmodel.g.dart b/mobile-app-new/lib/news/ui/search/search_viewmodel.g.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/search/search_viewmodel.g.dart rename to mobile-app-new/lib/news/ui/search/search_viewmodel.g.dart diff --git a/mobile-app-new/lib/ui/views/news/tag-feed/tag_feed_view.dart b/mobile-app-new/lib/news/ui/tag_feed/tag_feed_view.dart similarity index 85% rename from mobile-app-new/lib/ui/views/news/tag-feed/tag_feed_view.dart rename to mobile-app-new/lib/news/ui/tag_feed/tag_feed_view.dart index 8bfeafeb2..2faf97e89 100644 --- a/mobile-app-new/lib/ui/views/news/tag-feed/tag_feed_view.dart +++ b/mobile-app-new/lib/news/ui/tag_feed/tag_feed_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list.dart'; class NewsTagFeedView extends StatelessWidget { const NewsTagFeedView({super.key, required this.tagSlug, this.tagName}); diff --git a/mobile-app-new/lib/ui/views/news/widgets/back_to_top_button.dart b/mobile-app-new/lib/news/ui/widgets/back_to_top_button.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/widgets/back_to_top_button.dart rename to mobile-app-new/lib/news/ui/widgets/back_to_top_button.dart diff --git a/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart b/mobile-app-new/lib/news/ui/widgets/bookmark_button.dart similarity index 73% rename from mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart rename to mobile-app-new/lib/news/ui/widgets/bookmark_button.dart index d30a0d638..9d51d2b2c 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/bookmark_button.dart +++ b/mobile-app-new/lib/news/ui/widgets/bookmark_button.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; -import 'package:mobile_app_new/models/news/post_model.dart'; -import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_viewmodel.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/bottom_button.dart'; +import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; +import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/news/ui/widgets/bottom_button.dart'; class BookmarkButton extends ConsumerWidget { const BookmarkButton({super.key, required this.post}); diff --git a/mobile-app-new/lib/ui/views/news/widgets/bottom_button.dart b/mobile-app-new/lib/news/ui/widgets/bottom_button.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/widgets/bottom_button.dart rename to mobile-app-new/lib/news/ui/widgets/bottom_button.dart diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart b/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list.dart similarity index 93% rename from mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart rename to mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list.dart index edd79910c..967cb1d6d 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list.dart +++ b/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/post_tile.dart'; -import 'package:mobile_app_new/ui/core/widgets/error_retry.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list_state.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_tile.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; class PostFeedList extends ConsumerStatefulWidget { const PostFeedList({ diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart b/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.dart similarity index 94% rename from mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart rename to mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.dart index 1a19b050b..ae3b70daf 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.dart +++ b/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.dart @@ -1,6 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:mobile_app_new/models/news/post_model.dart'; -import 'package:mobile_app_new/services/news/api_service.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; +import 'package:mobile_app_new/news/services/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'post_feed_list_state.freezed.dart'; diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.freezed.dart b/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.freezed.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.freezed.dart rename to mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.freezed.dart diff --git a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart b/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.g.dart similarity index 97% rename from mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart rename to mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.g.dart index f166eb551..0899b3f05 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post-feed-list/post_feed_list_state.g.dart +++ b/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.g.dart @@ -50,7 +50,7 @@ final class NewsFeedNotifierProvider } } -String _$newsFeedNotifierHash() => r'f67d743e7fd7f603374d990de1f4dd336b91a6ff'; +String _$newsFeedNotifierHash() => r'b995464eda0195451d0942e58f38d176e44c7e34'; final class NewsFeedNotifierFamily extends $Family with diff --git a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart b/mobile-app-new/lib/news/ui/widgets/post_tile.dart similarity index 97% rename from mobile-app-new/lib/ui/views/news/widgets/post_tile.dart rename to mobile-app-new/lib/news/ui/widgets/post_tile.dart index efe4f8636..141b98b73 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/post_tile.dart +++ b/mobile-app-new/lib/news/ui/widgets/post_tile.dart @@ -3,10 +3,10 @@ import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/models/news/post_model.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/utils/date_utils.dart'; -import 'package:mobile_app_new/ui/views/news/widgets/tag_button.dart'; +import 'package:mobile_app_new/news/ui/widgets/tag_button.dart'; class PostTile extends StatelessWidget { const PostTile({super.key, required this.post}); diff --git a/mobile-app-new/lib/ui/views/news/widgets/search_result_tile.dart b/mobile-app-new/lib/news/ui/widgets/search_result_tile.dart similarity index 98% rename from mobile-app-new/lib/ui/views/news/widgets/search_result_tile.dart rename to mobile-app-new/lib/news/ui/widgets/search_result_tile.dart index d0299eb40..70714f438 100644 --- a/mobile-app-new/lib/ui/views/news/widgets/search_result_tile.dart +++ b/mobile-app-new/lib/news/ui/widgets/search_result_tile.dart @@ -1,7 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/models/news/search_post_model.dart'; +import 'package:mobile_app_new/news/models/search_post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/utils/date_utils.dart'; diff --git a/mobile-app-new/lib/ui/views/news/widgets/tag_button.dart b/mobile-app-new/lib/news/ui/widgets/tag_button.dart similarity index 100% rename from mobile-app-new/lib/ui/views/news/widgets/tag_button.dart rename to mobile-app-new/lib/news/ui/widgets/tag_button.dart diff --git a/mobile-app-new/lib/routing/learn.dart b/mobile-app-new/lib/routing/learn.dart index ab52fc8e8..cd7e19862 100644 --- a/mobile-app-new/lib/routing/learn.dart +++ b/mobile-app-new/lib/routing/learn.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:mobile_app_new/home_page.dart'; +import 'package:mobile_app_new/learn/ui/home_page.dart'; part 'learn.g.dart'; diff --git a/mobile-app-new/lib/routing/news.dart b/mobile-app-new/lib/routing/news.dart index a22744bf7..4ae23a372 100644 --- a/mobile-app-new/lib/routing/news.dart +++ b/mobile-app-new/lib/routing/news.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:mobile_app_new/models/news/bookmarked_post_model.dart'; -import 'package:mobile_app_new/ui/views/news/bookmark-feed/bookmark_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/bookmark-post/bookmark_post_view.dart'; -import 'package:mobile_app_new/ui/views/news/feed/feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/post/post_view.dart'; -import 'package:mobile_app_new/ui/views/news/search/search_view.dart'; -import 'package:mobile_app_new/models/news/author_model.dart'; -import 'package:mobile_app_new/ui/views/news/author-feed/author_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/tag-feed/tag_feed_view.dart'; -import 'package:mobile_app_new/ui/views/news/news_shell.dart'; -import 'package:mobile_app_new/ui/views/news/image/image_view.dart'; +import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; +import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_view.dart'; +import 'package:mobile_app_new/news/ui/bookmark_post/bookmark_post_view.dart'; +import 'package:mobile_app_new/news/ui/feed/feed_view.dart'; +import 'package:mobile_app_new/news/ui/post/post_view.dart'; +import 'package:mobile_app_new/news/ui/search/search_view.dart'; +import 'package:mobile_app_new/news/models/author_model.dart'; +import 'package:mobile_app_new/news/ui/author_feed/author_feed_view.dart'; +import 'package:mobile_app_new/news/ui/tag_feed/tag_feed_view.dart'; +import 'package:mobile_app_new/news/ui/news_shell.dart'; +import 'package:mobile_app_new/news/ui/image/image_view.dart'; part 'news.g.dart'; diff --git a/mobile-app-new/lib/ui/core/drawer/drawer.dart b/mobile-app-new/lib/widgets/drawer/drawer.dart similarity index 92% rename from mobile-app-new/lib/ui/core/drawer/drawer.dart rename to mobile-app-new/lib/widgets/drawer/drawer.dart index 2e4bf484b..b96956610 100644 --- a/mobile-app-new/lib/ui/core/drawer/drawer.dart +++ b/mobile-app-new/lib/widgets/drawer/drawer.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/routing/learn.dart'; import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/ui/core/drawer/drawer_tile.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer_tile.dart'; class DrawerWidget extends StatelessWidget { const DrawerWidget({super.key}); diff --git a/mobile-app-new/lib/ui/core/drawer/drawer_tile.dart b/mobile-app-new/lib/widgets/drawer/drawer_tile.dart similarity index 100% rename from mobile-app-new/lib/ui/core/drawer/drawer_tile.dart rename to mobile-app-new/lib/widgets/drawer/drawer_tile.dart diff --git a/mobile-app-new/lib/ui/core/widgets/error_retry.dart b/mobile-app-new/lib/widgets/error_retry.dart similarity index 100% rename from mobile-app-new/lib/ui/core/widgets/error_retry.dart rename to mobile-app-new/lib/widgets/error_retry.dart diff --git a/mobile-app-new/lib/ui/core/html_handler/html_handler.dart b/mobile-app-new/lib/widgets/html_handler/html_handler.dart similarity index 100% rename from mobile-app-new/lib/ui/core/html_handler/html_handler.dart rename to mobile-app-new/lib/widgets/html_handler/html_handler.dart From 66109b8f52e4ca56931d0a503303b08e7a9cd76d Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 6 Sep 2026 14:34:26 +0530 Subject: [PATCH 27/52] fix: bug fixes and perf optimisations --- .../bookmarks_controller.dart} | 2 +- .../bookmarks_controller.g.dart} | 2 +- .../feed_controller.dart} | 45 +-- .../feed_controller.freezed.dart} | 354 +++++++++++++++++- .../feed_controller.g.dart} | 29 +- .../lib/news/models/post_summary_model.dart | 23 ++ .../models/post_summary_model.freezed.dart | 346 +++++++++++++++++ .../lib/news/models/post_summary_model.g.dart | 36 ++ .../lib/news/repositories/api_repository.dart | 170 ++++----- .../lib/news/services/api_service.dart | 55 ++- .../news/ui/author_feed/author_feed_view.dart | 5 +- .../ui/author_feed/author_feed_viewmodel.dart | 2 +- .../author_feed/author_feed_viewmodel.g.dart | 2 +- .../ui/bookmark_feed/bookmark_feed_view.dart | 2 +- .../ui/bookmark_post/bookmark_post_view.dart | 28 +- .../lib/news/ui/feed/feed_view.dart | 6 +- .../lib/news/ui/post/post_view.dart | 100 ++--- .../lib/news/ui/post/post_viewmodel.dart | 2 +- .../lib/news/ui/post/post_viewmodel.g.dart | 2 +- .../lib/news/ui/search/search_view.dart | 55 ++- .../lib/news/ui/tag_feed/tag_feed_view.dart | 5 +- .../news/ui/widgets/back_to_top_button.dart | 79 +++- .../lib/news/ui/widgets/bookmark_button.dart | 2 +- .../{post_feed_list => }/post_feed_list.dart | 15 +- .../lib/news/ui/widgets/post_tile.dart | 10 +- 25 files changed, 1020 insertions(+), 357 deletions(-) rename mobile-app-new/lib/news/{ui/bookmark_feed/bookmark_feed_viewmodel.dart => controllers/bookmarks_controller.dart} (94%) rename mobile-app-new/lib/news/{ui/bookmark_feed/bookmark_feed_viewmodel.g.dart => controllers/bookmarks_controller.g.dart} (97%) rename mobile-app-new/lib/news/{ui/widgets/post_feed_list/post_feed_list_state.dart => controllers/feed_controller.dart} (59%) rename mobile-app-new/lib/news/{ui/widgets/post_feed_list/post_feed_list_state.freezed.dart => controllers/feed_controller.freezed.dart} (50%) rename mobile-app-new/lib/news/{ui/widgets/post_feed_list/post_feed_list_state.g.dart => controllers/feed_controller.g.dart} (72%) create mode 100644 mobile-app-new/lib/news/models/post_summary_model.dart create mode 100644 mobile-app-new/lib/news/models/post_summary_model.freezed.dart create mode 100644 mobile-app-new/lib/news/models/post_summary_model.g.dart rename mobile-app-new/lib/news/ui/widgets/{post_feed_list => }/post_feed_list.dart (90%) diff --git a/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart b/mobile-app-new/lib/news/controllers/bookmarks_controller.dart similarity index 94% rename from mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart rename to mobile-app-new/lib/news/controllers/bookmarks_controller.dart index 359d282d0..e7ea25bee 100644 --- a/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart +++ b/mobile-app-new/lib/news/controllers/bookmarks_controller.dart @@ -2,7 +2,7 @@ import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; import 'package:mobile_app_new/news/services/bookmark_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'bookmark_feed_viewmodel.g.dart'; +part 'bookmarks_controller.g.dart'; @riverpod class NewsBookmarksNotifier extends _$NewsBookmarksNotifier { diff --git a/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.g.dart b/mobile-app-new/lib/news/controllers/bookmarks_controller.g.dart similarity index 97% rename from mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.g.dart rename to mobile-app-new/lib/news/controllers/bookmarks_controller.g.dart index e28d8197b..aeb78e67f 100644 --- a/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_viewmodel.g.dart +++ b/mobile-app-new/lib/news/controllers/bookmarks_controller.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'bookmark_feed_viewmodel.dart'; +part of 'bookmarks_controller.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.dart b/mobile-app-new/lib/news/controllers/feed_controller.dart similarity index 59% rename from mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.dart rename to mobile-app-new/lib/news/controllers/feed_controller.dart index ae3b70daf..ad7a8df28 100644 --- a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.dart +++ b/mobile-app-new/lib/news/controllers/feed_controller.dart @@ -1,15 +1,23 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:mobile_app_new/news/models/post_model.dart'; +import 'package:mobile_app_new/news/models/post_summary_model.dart'; import 'package:mobile_app_new/news/services/api_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'post_feed_list_state.freezed.dart'; -part 'post_feed_list_state.g.dart'; +part 'feed_controller.freezed.dart'; +part 'feed_controller.g.dart'; + +// Which posts a feed shows. Exactly one mode, checked by the compiler. +@freezed +sealed class NewsFeedSource with _$NewsFeedSource { + const factory NewsFeedSource.all() = AllPosts; + const factory NewsFeedSource.tag(String slug) = TagPosts; + const factory NewsFeedSource.author(String id) = AuthorPosts; +} @freezed abstract class NewsFeedState with _$NewsFeedState { const factory NewsFeedState({ - @Default([]) List posts, + @Default([]) List posts, @Default('') String cursor, @Default(true) bool hasNextPage, @Default(false) bool isLoadingMore, @@ -21,11 +29,8 @@ abstract class NewsFeedState with _$NewsFeedState { @riverpod class NewsFeedNotifier extends _$NewsFeedNotifier { @override - FutureOr build({ - String tagSlug = '', - String authorId = '', - }) async { - final page = await _fetch(''); + FutureOr build(NewsFeedSource source) async { + final page = await _fetch(ref.watch(newsApiServiceProvider), ''); return NewsFeedState( posts: page.posts, @@ -34,17 +39,12 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { ); } - Future _fetch(String cursor) { - final service = ref.read(newsApiServiceProvider); - - if (authorId.isNotEmpty) { - return service.getPostsByAuthor(authorId, afterCursor: cursor); - } - if (tagSlug.isNotEmpty) { - return service.getPostsByTag(tagSlug, afterCursor: cursor); - } - return service.getAllPosts(afterCursor: cursor); - } + Future _fetch(NewsApiService service, String cursor) => + switch (source) { + AllPosts() => service.getAllPosts(afterCursor: cursor), + TagPosts(:final slug) => service.getPostsByTag(slug, afterCursor: cursor), + AuthorPosts(:final id) => service.getPostsByAuthor(id, afterCursor: cursor), + }; Future fetchNextPage({bool isRetry = false}) async { final current = state.value; @@ -58,7 +58,10 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { ); try { - final page = await _fetch(current.cursor); + final page = await _fetch( + ref.read(newsApiServiceProvider), + current.cursor, + ); state = AsyncData( current.copyWith( diff --git a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.freezed.dart b/mobile-app-new/lib/news/controllers/feed_controller.freezed.dart similarity index 50% rename from mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.freezed.dart rename to mobile-app-new/lib/news/controllers/feed_controller.freezed.dart index 9b8afd86f..870033411 100644 --- a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.freezed.dart +++ b/mobile-app-new/lib/news/controllers/feed_controller.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'post_feed_list_state.dart'; +part of 'feed_controller.dart'; // ************************************************************************** // FreezedGenerator @@ -11,10 +11,340 @@ part of 'post_feed_list_state.dart'; // dart format off T _$identity(T value) => value; +/// @nodoc +mixin _$NewsFeedSource { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is NewsFeedSource); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'NewsFeedSource()'; +} + + +} + +/// @nodoc +class $NewsFeedSourceCopyWith<$Res> { +$NewsFeedSourceCopyWith(NewsFeedSource _, $Res Function(NewsFeedSource) __); +} + + +/// Adds pattern-matching-related methods to [NewsFeedSource]. +extension NewsFeedSourcePatterns on NewsFeedSource { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( AllPosts value)? all,TResult Function( TagPosts value)? tag,TResult Function( AuthorPosts value)? author,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case AllPosts() when all != null: +return all(_that);case TagPosts() when tag != null: +return tag(_that);case AuthorPosts() when author != null: +return author(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( AllPosts value) all,required TResult Function( TagPosts value) tag,required TResult Function( AuthorPosts value) author,}){ +final _that = this; +switch (_that) { +case AllPosts(): +return all(_that);case TagPosts(): +return tag(_that);case AuthorPosts(): +return author(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( AllPosts value)? all,TResult? Function( TagPosts value)? tag,TResult? Function( AuthorPosts value)? author,}){ +final _that = this; +switch (_that) { +case AllPosts() when all != null: +return all(_that);case TagPosts() when tag != null: +return tag(_that);case AuthorPosts() when author != null: +return author(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function()? all,TResult Function( String slug)? tag,TResult Function( String id)? author,required TResult orElse(),}) {final _that = this; +switch (_that) { +case AllPosts() when all != null: +return all();case TagPosts() when tag != null: +return tag(_that.slug);case AuthorPosts() when author != null: +return author(_that.id);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function() all,required TResult Function( String slug) tag,required TResult Function( String id) author,}) {final _that = this; +switch (_that) { +case AllPosts(): +return all();case TagPosts(): +return tag(_that.slug);case AuthorPosts(): +return author(_that.id);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function()? all,TResult? Function( String slug)? tag,TResult? Function( String id)? author,}) {final _that = this; +switch (_that) { +case AllPosts() when all != null: +return all();case TagPosts() when tag != null: +return tag(_that.slug);case AuthorPosts() when author != null: +return author(_that.id);case _: + return null; + +} +} + +} + +/// @nodoc + + +class AllPosts implements NewsFeedSource { + const AllPosts(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AllPosts); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'NewsFeedSource.all()'; +} + + +} + + + + +/// @nodoc + + +class TagPosts implements NewsFeedSource { + const TagPosts(this.slug); + + + final String slug; + +/// Create a copy of NewsFeedSource +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$TagPostsCopyWith get copyWith => _$TagPostsCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is TagPosts&&(identical(other.slug, slug) || other.slug == slug)); +} + + +@override +int get hashCode => Object.hash(runtimeType,slug); + +@override +String toString() { + return 'NewsFeedSource.tag(slug: $slug)'; +} + + +} + +/// @nodoc +abstract mixin class $TagPostsCopyWith<$Res> implements $NewsFeedSourceCopyWith<$Res> { + factory $TagPostsCopyWith(TagPosts value, $Res Function(TagPosts) _then) = _$TagPostsCopyWithImpl; +@useResult +$Res call({ + String slug +}); + + + + +} +/// @nodoc +class _$TagPostsCopyWithImpl<$Res> + implements $TagPostsCopyWith<$Res> { + _$TagPostsCopyWithImpl(this._self, this._then); + + final TagPosts _self; + final $Res Function(TagPosts) _then; + +/// Create a copy of NewsFeedSource +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? slug = null,}) { + return _then(TagPosts( +null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class AuthorPosts implements NewsFeedSource { + const AuthorPosts(this.id); + + + final String id; + +/// Create a copy of NewsFeedSource +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthorPostsCopyWith get copyWith => _$AuthorPostsCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthorPosts&&(identical(other.id, id) || other.id == id)); +} + + +@override +int get hashCode => Object.hash(runtimeType,id); + +@override +String toString() { + return 'NewsFeedSource.author(id: $id)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthorPostsCopyWith<$Res> implements $NewsFeedSourceCopyWith<$Res> { + factory $AuthorPostsCopyWith(AuthorPosts value, $Res Function(AuthorPosts) _then) = _$AuthorPostsCopyWithImpl; +@useResult +$Res call({ + String id +}); + + + + +} +/// @nodoc +class _$AuthorPostsCopyWithImpl<$Res> + implements $AuthorPostsCopyWith<$Res> { + _$AuthorPostsCopyWithImpl(this._self, this._then); + + final AuthorPosts _self; + final $Res Function(AuthorPosts) _then; + +/// Create a copy of NewsFeedSource +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? id = null,}) { + return _then(AuthorPosts( +null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + /// @nodoc mixin _$NewsFeedState { - List get posts; String get cursor; bool get hasNextPage; bool get isLoadingMore; Object? get loadMoreError; + List get posts; String get cursor; bool get hasNextPage; bool get isLoadingMore; Object? get loadMoreError; /// Create a copy of NewsFeedState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -45,7 +375,7 @@ abstract mixin class $NewsFeedStateCopyWith<$Res> { factory $NewsFeedStateCopyWith(NewsFeedState value, $Res Function(NewsFeedState) _then) = _$NewsFeedStateCopyWithImpl; @useResult $Res call({ - List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError + List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError }); @@ -65,7 +395,7 @@ class _$NewsFeedStateCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({Object? posts = null,Object? cursor = null,Object? hasNextPage = null,Object? isLoadingMore = null,Object? loadMoreError = freezed,}) { return _then(_self.copyWith( posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable -as List,cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as List,cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable as String,hasNextPage: null == hasNextPage ? _self.hasNextPage : hasNextPage // ignore: cast_nullable_to_non_nullable as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable as bool,loadMoreError: freezed == loadMoreError ? _self.loadMoreError : loadMoreError , @@ -153,7 +483,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _NewsFeedState() when $default != null: return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: @@ -174,7 +504,7 @@ return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_ /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError) $default,) {final _that = this; switch (_that) { case _NewsFeedState(): return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: @@ -194,7 +524,7 @@ return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_ /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,) {final _that = this; switch (_that) { case _NewsFeedState() when $default != null: return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: @@ -209,11 +539,11 @@ return $default(_that.posts,_that.cursor,_that.hasNextPage,_that.isLoadingMore,_ class _NewsFeedState implements NewsFeedState { - const _NewsFeedState({final List posts = const [], this.cursor = '', this.hasNextPage = true, this.isLoadingMore = false, this.loadMoreError}): _posts = posts; + const _NewsFeedState({final List posts = const [], this.cursor = '', this.hasNextPage = true, this.isLoadingMore = false, this.loadMoreError}): _posts = posts; - final List _posts; -@override@JsonKey() List get posts { + final List _posts; +@override@JsonKey() List get posts { if (_posts is EqualUnmodifiableListView) return _posts; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_posts); @@ -254,7 +584,7 @@ abstract mixin class _$NewsFeedStateCopyWith<$Res> implements $NewsFeedStateCopy factory _$NewsFeedStateCopyWith(_NewsFeedState value, $Res Function(_NewsFeedState) _then) = __$NewsFeedStateCopyWithImpl; @override @useResult $Res call({ - List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError + List posts, String cursor, bool hasNextPage, bool isLoadingMore, Object? loadMoreError }); @@ -274,7 +604,7 @@ class __$NewsFeedStateCopyWithImpl<$Res> @override @pragma('vm:prefer-inline') $Res call({Object? posts = null,Object? cursor = null,Object? hasNextPage = null,Object? isLoadingMore = null,Object? loadMoreError = freezed,}) { return _then(_NewsFeedState( posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable -as List,cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as List,cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable as String,hasNextPage: null == hasNextPage ? _self.hasNextPage : hasNextPage // ignore: cast_nullable_to_non_nullable as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable as bool,loadMoreError: freezed == loadMoreError ? _self.loadMoreError : loadMoreError , diff --git a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.g.dart b/mobile-app-new/lib/news/controllers/feed_controller.g.dart similarity index 72% rename from mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.g.dart rename to mobile-app-new/lib/news/controllers/feed_controller.g.dart index 0899b3f05..22ea06027 100644 --- a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list_state.g.dart +++ b/mobile-app-new/lib/news/controllers/feed_controller.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'post_feed_list_state.dart'; +part of 'feed_controller.dart'; // ************************************************************************** // RiverpodGenerator @@ -16,7 +16,7 @@ final class NewsFeedNotifierProvider extends $AsyncNotifierProvider { NewsFeedNotifierProvider._({ required NewsFeedNotifierFamily super.from, - required ({String tagSlug, String authorId}) super.argument, + required NewsFeedSource super.argument, }) : super( retry: null, name: r'newsFeedProvider', @@ -32,7 +32,7 @@ final class NewsFeedNotifierProvider String toString() { return r'newsFeedProvider' '' - '$argument'; + '($argument)'; } @$internal @@ -50,7 +50,7 @@ final class NewsFeedNotifierProvider } } -String _$newsFeedNotifierHash() => r'b995464eda0195451d0942e58f38d176e44c7e34'; +String _$newsFeedNotifierHash() => r'7365cd1e53036b584af9c460c278883f8ad28e85'; final class NewsFeedNotifierFamily extends $Family with @@ -59,7 +59,7 @@ final class NewsFeedNotifierFamily extends $Family AsyncValue, NewsFeedState, FutureOr, - ({String tagSlug, String authorId}) + NewsFeedSource > { NewsFeedNotifierFamily._() : super( @@ -70,22 +70,18 @@ final class NewsFeedNotifierFamily extends $Family isAutoDispose: true, ); - NewsFeedNotifierProvider call({String tagSlug = '', String authorId = ''}) => - NewsFeedNotifierProvider._( - argument: (tagSlug: tagSlug, authorId: authorId), - from: this, - ); + NewsFeedNotifierProvider call(NewsFeedSource source) => + NewsFeedNotifierProvider._(argument: source, from: this); @override String toString() => r'newsFeedProvider'; } abstract class _$NewsFeedNotifier extends $AsyncNotifier { - late final _$args = ref.$arg as ({String tagSlug, String authorId}); - String get tagSlug => _$args.tagSlug; - String get authorId => _$args.authorId; + late final _$args = ref.$arg as NewsFeedSource; + NewsFeedSource get source => _$args; - FutureOr build({String tagSlug = '', String authorId = ''}); + FutureOr build(NewsFeedSource source); @$mustCallSuper @override WhenComplete runBuild() { @@ -98,9 +94,6 @@ abstract class _$NewsFeedNotifier extends $AsyncNotifier { Object?, Object? >; - return element.handleCreate( - ref, - () => build(tagSlug: _$args.tagSlug, authorId: _$args.authorId), - ); + return element.handleCreate(ref, () => build(_$args)); } } diff --git a/mobile-app-new/lib/news/models/post_summary_model.dart b/mobile-app-new/lib/news/models/post_summary_model.dart new file mode 100644 index 000000000..9221a6c4a --- /dev/null +++ b/mobile-app-new/lib/news/models/post_summary_model.dart @@ -0,0 +1,23 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/news/models/author_model.dart'; +import 'package:mobile_app_new/news/models/post_model.dart'; + +part 'post_summary_model.freezed.dart'; +part 'post_summary_model.g.dart'; + +@freezed +abstract class PostSummary with _$PostSummary { + const factory PostSummary({ + required String id, + required String slug, + required String title, + required Author author, + @Default([]) List tags, + CoverImage? coverImage, + required int readTimeInMinutes, + required String publishedAt, + }) = _PostSummary; + + factory PostSummary.fromJson(Map json) => + _$PostSummaryFromJson(json); +} diff --git a/mobile-app-new/lib/news/models/post_summary_model.freezed.dart b/mobile-app-new/lib/news/models/post_summary_model.freezed.dart new file mode 100644 index 000000000..35656c6c5 --- /dev/null +++ b/mobile-app-new/lib/news/models/post_summary_model.freezed.dart @@ -0,0 +1,346 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'post_summary_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$PostSummary { + + String get id; String get slug; String get title; Author get author; List get tags; CoverImage? get coverImage; int get readTimeInMinutes; String get publishedAt; +/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PostSummaryCopyWith get copyWith => _$PostSummaryCopyWithImpl(this as PostSummary, _$identity); + + /// Serializes this PostSummary to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PostSummary&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.title, title) || other.title == title)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.readTimeInMinutes, readTimeInMinutes) || other.readTimeInMinutes == readTimeInMinutes)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,slug,title,author,const DeepCollectionEquality().hash(tags),coverImage,readTimeInMinutes,publishedAt); + +@override +String toString() { + return 'PostSummary(id: $id, slug: $slug, title: $title, author: $author, tags: $tags, coverImage: $coverImage, readTimeInMinutes: $readTimeInMinutes, publishedAt: $publishedAt)'; +} + + +} + +/// @nodoc +abstract mixin class $PostSummaryCopyWith<$Res> { + factory $PostSummaryCopyWith(PostSummary value, $Res Function(PostSummary) _then) = _$PostSummaryCopyWithImpl; +@useResult +$Res call({ + String id, String slug, String title, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, String publishedAt +}); + + +$AuthorCopyWith<$Res> get author;$CoverImageCopyWith<$Res>? get coverImage; + +} +/// @nodoc +class _$PostSummaryCopyWithImpl<$Res> + implements $PostSummaryCopyWith<$Res> { + _$PostSummaryCopyWithImpl(this._self, this._then); + + final PostSummary _self; + final $Res Function(PostSummary) _then; + +/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? publishedAt = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as Author,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable +as List,coverImage: freezed == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable +as CoverImage?,readTimeInMinutes: null == readTimeInMinutes ? _self.readTimeInMinutes : readTimeInMinutes // ignore: cast_nullable_to_non_nullable +as int,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String, + )); +} +/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AuthorCopyWith<$Res> get author { + + return $AuthorCopyWith<$Res>(_self.author, (value) { + return _then(_self.copyWith(author: value)); + }); +}/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CoverImageCopyWith<$Res>? get coverImage { + if (_self.coverImage == null) { + return null; + } + + return $CoverImageCopyWith<$Res>(_self.coverImage!, (value) { + return _then(_self.copyWith(coverImage: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [PostSummary]. +extension PostSummaryPatterns on PostSummary { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PostSummary value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PostSummary() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PostSummary value) $default,){ +final _that = this; +switch (_that) { +case _PostSummary(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PostSummary value)? $default,){ +final _that = this; +switch (_that) { +case _PostSummary() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String slug, String title, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, String publishedAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PostSummary() when $default != null: +return $default(_that.id,_that.slug,_that.title,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.publishedAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String slug, String title, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, String publishedAt) $default,) {final _that = this; +switch (_that) { +case _PostSummary(): +return $default(_that.id,_that.slug,_that.title,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.publishedAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String slug, String title, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, String publishedAt)? $default,) {final _that = this; +switch (_that) { +case _PostSummary() when $default != null: +return $default(_that.id,_that.slug,_that.title,_that.author,_that.tags,_that.coverImage,_that.readTimeInMinutes,_that.publishedAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _PostSummary implements PostSummary { + const _PostSummary({required this.id, required this.slug, required this.title, required this.author, final List tags = const [], this.coverImage, required this.readTimeInMinutes, required this.publishedAt}): _tags = tags; + factory _PostSummary.fromJson(Map json) => _$PostSummaryFromJson(json); + +@override final String id; +@override final String slug; +@override final String title; +@override final Author author; + final List _tags; +@override@JsonKey() List get tags { + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tags); +} + +@override final CoverImage? coverImage; +@override final int readTimeInMinutes; +@override final String publishedAt; + +/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PostSummaryCopyWith<_PostSummary> get copyWith => __$PostSummaryCopyWithImpl<_PostSummary>(this, _$identity); + +@override +Map toJson() { + return _$PostSummaryToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PostSummary&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.title, title) || other.title == title)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.coverImage, coverImage) || other.coverImage == coverImage)&&(identical(other.readTimeInMinutes, readTimeInMinutes) || other.readTimeInMinutes == readTimeInMinutes)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,slug,title,author,const DeepCollectionEquality().hash(_tags),coverImage,readTimeInMinutes,publishedAt); + +@override +String toString() { + return 'PostSummary(id: $id, slug: $slug, title: $title, author: $author, tags: $tags, coverImage: $coverImage, readTimeInMinutes: $readTimeInMinutes, publishedAt: $publishedAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$PostSummaryCopyWith<$Res> implements $PostSummaryCopyWith<$Res> { + factory _$PostSummaryCopyWith(_PostSummary value, $Res Function(_PostSummary) _then) = __$PostSummaryCopyWithImpl; +@override @useResult +$Res call({ + String id, String slug, String title, Author author, List tags, CoverImage? coverImage, int readTimeInMinutes, String publishedAt +}); + + +@override $AuthorCopyWith<$Res> get author;@override $CoverImageCopyWith<$Res>? get coverImage; + +} +/// @nodoc +class __$PostSummaryCopyWithImpl<$Res> + implements _$PostSummaryCopyWith<$Res> { + __$PostSummaryCopyWithImpl(this._self, this._then); + + final _PostSummary _self; + final $Res Function(_PostSummary) _then; + +/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? title = null,Object? author = null,Object? tags = null,Object? coverImage = freezed,Object? readTimeInMinutes = null,Object? publishedAt = null,}) { + return _then(_PostSummary( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,author: null == author ? _self.author : author // ignore: cast_nullable_to_non_nullable +as Author,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable +as List,coverImage: freezed == coverImage ? _self.coverImage : coverImage // ignore: cast_nullable_to_non_nullable +as CoverImage?,readTimeInMinutes: null == readTimeInMinutes ? _self.readTimeInMinutes : readTimeInMinutes // ignore: cast_nullable_to_non_nullable +as int,publishedAt: null == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AuthorCopyWith<$Res> get author { + + return $AuthorCopyWith<$Res>(_self.author, (value) { + return _then(_self.copyWith(author: value)); + }); +}/// Create a copy of PostSummary +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CoverImageCopyWith<$Res>? get coverImage { + if (_self.coverImage == null) { + return null; + } + + return $CoverImageCopyWith<$Res>(_self.coverImage!, (value) { + return _then(_self.copyWith(coverImage: value)); + }); +} +} + +// dart format on diff --git a/mobile-app-new/lib/news/models/post_summary_model.g.dart b/mobile-app-new/lib/news/models/post_summary_model.g.dart new file mode 100644 index 000000000..9b64735fb --- /dev/null +++ b/mobile-app-new/lib/news/models/post_summary_model.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'post_summary_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_PostSummary _$PostSummaryFromJson(Map json) => _PostSummary( + id: json['id'] as String, + slug: json['slug'] as String, + title: json['title'] as String, + author: Author.fromJson(json['author'] as Map), + tags: + (json['tags'] as List?) + ?.map((e) => Tag.fromJson(e as Map)) + .toList() ?? + const [], + coverImage: json['coverImage'] == null + ? null + : CoverImage.fromJson(json['coverImage'] as Map), + readTimeInMinutes: (json['readTimeInMinutes'] as num).toInt(), + publishedAt: json['publishedAt'] as String, +); + +Map _$PostSummaryToJson(_PostSummary instance) => + { + 'id': instance.id, + 'slug': instance.slug, + 'title': instance.title, + 'author': instance.author, + 'tags': instance.tags, + 'coverImage': instance.coverImage, + 'readTimeInMinutes': instance.readTimeInMinutes, + 'publishedAt': instance.publishedAt, + }; diff --git a/mobile-app-new/lib/news/repositories/api_repository.dart b/mobile-app-new/lib/news/repositories/api_repository.dart index dc33212ca..c1983848c 100644 --- a/mobile-app-new/lib/news/repositories/api_repository.dart +++ b/mobile-app-new/lib/news/repositories/api_repository.dart @@ -14,12 +14,11 @@ const _apiUrl = 'https://gql-beta.hashnode.com'; const postsPerPage = 20; -const postFieldsFragment = r''' - fragment PostFields on Post { +const _postListFields = r''' + fragment PostListFields on Post { id slug title - url author { id username @@ -44,15 +43,24 @@ const postFieldsFragment = r''' url } readTimeInMinutes + publishedAt + } + '''; + +const _postDetailFields = + _postListFields + + r''' + fragment PostDetailFields on Post { + ...PostListFields + url content { html } - publishedAt } '''; const _getAllPostsQuery = - postFieldsFragment + + _postListFields + r''' query GetAllPosts($publicationId: ObjectId!, $first: Int!, $after: String) { publication(id: $publicationId) { @@ -60,7 +68,7 @@ const _getAllPostsQuery = posts(first: $first, after: $after) { edges { node { - ...PostFields + ...PostListFields } } pageInfo { @@ -93,13 +101,13 @@ const _getAuthorQuery = r''' '''; const _getPostsByAuthorQuery = - postFieldsFragment + + _postListFields + r''' query GetPostsByAuthorQuery($first: Int!, $after: String, $filter: SearchPostsOfPublicationFilter!) { searchPostsOfPublication(first: $first, after: $after, filter: $filter) { edges { node { - ...PostFields + ...PostListFields } } pageInfo { @@ -111,7 +119,7 @@ const _getPostsByAuthorQuery = '''; const _getPostsByTagQuery = - postFieldsFragment + + _postListFields + r''' query GetPostsByTagQuery($publicationId: ObjectId!, $first: Int!, $after: String, $filter: PublicationPostConnectionFilter!) { publication(id: $publicationId) { @@ -119,7 +127,7 @@ const _getPostsByTagQuery = posts(first: $first, after: $after, filter: $filter) { edges { node { - ...PostFields + ...PostListFields } } pageInfo { @@ -132,12 +140,13 @@ const _getPostsByTagQuery = '''; const _getPostBySlugQuery = - postFieldsFragment + + _postDetailFields + r''' query GetPostBySlug($publicationId: ObjectId!, $slug: String!) { publication(id: $publicationId) { + id post(slug: $slug) { - ...PostFields + ...PostDetailFields } } } @@ -153,120 +162,81 @@ class NewsApiRepository { ); final String _publicationId = dotenv.get('HASHNODE_PUBLICATION_ID'); - Future getAllPosts({String afterCursor = ''}) async { + Future> _query( + String document, + Map variables, + ) async { final result = await _client.query( - QueryOptions( - document: gql(_getAllPostsQuery), - variables: { - 'publicationId': _publicationId, - 'first': postsPerPage, - 'after': afterCursor, - }, - ), + QueryOptions(document: gql(document), variables: variables), ); if (result.hasException) { throw Exception(result.exception.toString()); } - final data = result.data!['publication']['posts']; - return ( - items: (data['edges'] as List).cast>(), - endCursor: data['pageInfo']['endCursor'] as String, - hasNextPage: data['pageInfo']['hasNextPage'] as bool, - ); + return result.data!; } - Future> getPostBySlug(String slug) async { - final result = await _client.query( - QueryOptions( - document: gql(_getPostBySlugQuery), - variables: { - 'publicationId': _publicationId, - 'slug': slug, - }, - ), - ); + RawPaginatedResponse _toPage(Map connection) => ( + items: (connection['edges'] as List).cast>(), + endCursor: connection['pageInfo']['endCursor'] as String, + hasNextPage: connection['pageInfo']['hasNextPage'] as bool, + ); - if (result.hasException) { - throw Exception(result.exception.toString()); - } + Future getAllPosts({String afterCursor = ''}) async { + final data = await _query(_getAllPostsQuery, { + 'publicationId': _publicationId, + 'first': postsPerPage, + 'after': afterCursor, + }); - return result.data!['publication']['post'] as Map; + return _toPage(data['publication']['posts'] as Map); } - Future> getAuthor(String authorSlug) async { - final result = await _client.query( - QueryOptions( - document: gql(_getAuthorQuery), - variables: {'authorSlug': authorSlug}, - ), - ); + Future> getPostBySlug(String slug) async { + final data = await _query(_getPostBySlugQuery, { + 'publicationId': _publicationId, + 'slug': slug, + }); - if (result.hasException) { - throw Exception(result.exception.toString()); - } + return data['publication']['post'] as Map; + } + + Future> getAuthor(String authorSlug) async { + final data = await _query(_getAuthorQuery, {'authorSlug': authorSlug}); - return result.data!['user'] as Map; + return data['user'] as Map; } Future getPostsByAuthor( String authorId, { String afterCursor = '', }) async { - final result = await _client.query( - QueryOptions( - document: gql(_getPostsByAuthorQuery), - variables: { - 'first': postsPerPage, - 'after': afterCursor, - 'filter': { - 'publicationId': _publicationId, - 'authorIds': [authorId], - }, - }, - ), - ); - - if (result.hasException) { - throw Exception(result.exception.toString()); - } - - final data = result.data!['searchPostsOfPublication']; - return ( - items: (data['edges'] as List).cast>(), - endCursor: data['pageInfo']['endCursor'] as String, - hasNextPage: data['pageInfo']['hasNextPage'] as bool, - ); + final data = await _query(_getPostsByAuthorQuery, { + 'first': postsPerPage, + 'after': afterCursor, + 'filter': { + 'publicationId': _publicationId, + 'authorIds': [authorId], + }, + }); + + return _toPage(data['searchPostsOfPublication'] as Map); } Future getPostsByTag( String tagSlug, { String afterCursor = '', }) async { - final result = await _client.query( - QueryOptions( - document: gql(_getPostsByTagQuery), - variables: { - 'publicationId': _publicationId, - 'first': postsPerPage, - 'after': afterCursor, - 'filter': { - 'tagSlugs': [tagSlug], - }, - }, - ), - ); - - if (result.hasException) { - throw Exception(result.exception.toString()); - } - - final data = result.data!['publication']['posts']; - return ( - items: (data['edges'] as List).cast>(), - endCursor: data['pageInfo']['endCursor'] as String, - hasNextPage: data['pageInfo']['hasNextPage'] as bool, - ); + final data = await _query(_getPostsByTagQuery, { + 'publicationId': _publicationId, + 'first': postsPerPage, + 'after': afterCursor, + 'filter': { + 'tagSlugs': [tagSlug], + }, + }); + + return _toPage(data['publication']['posts'] as Map); } } diff --git a/mobile-app-new/lib/news/services/api_service.dart b/mobile-app-new/lib/news/services/api_service.dart index 5b231f867..3e5b2ed11 100644 --- a/mobile-app-new/lib/news/services/api_service.dart +++ b/mobile-app-new/lib/news/services/api_service.dart @@ -1,5 +1,6 @@ import 'package:mobile_app_new/news/models/author_model.dart'; import 'package:mobile_app_new/news/models/post_model.dart'; +import 'package:mobile_app_new/news/models/post_summary_model.dart'; import 'package:mobile_app_new/news/repositories/api_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -8,7 +9,7 @@ part 'api_service.g.dart'; typedef PostsPage = ({ String endCursor, bool hasNextPage, - List posts, + List posts, }); @riverpod @@ -22,43 +23,37 @@ class NewsApiService { NewsApiService(this._repo); - Future getAllPosts({String afterCursor = ''}) async { - final raw = await _repo.getAllPosts(afterCursor: afterCursor); - final posts = raw.items - .map((edge) => Post.fromJson(edge['node'] as Map)) - .toList(); - return (posts: posts, endCursor: raw.endCursor, hasNextPage: raw.hasNextPage); - } + // The posts of a connection arrive wrapped in edge nodes. + PostsPage _toPostsPage(RawPaginatedResponse raw) => ( + posts: raw.items + .map( + (edge) => PostSummary.fromJson(edge['node'] as Map), + ) + .toList(), + endCursor: raw.endCursor, + hasNextPage: raw.hasNextPage, + ); - Future getPostBySlug(String slug) async { - final raw = await _repo.getPostBySlug(slug); - return Post.fromJson(raw); - } + Future getAllPosts({String afterCursor = ''}) async => + _toPostsPage(await _repo.getAllPosts(afterCursor: afterCursor)); - Future getAuthor(String authorSlug) async { - final raw = await _repo.getAuthor(authorSlug); - return Author.fromJson(raw); - } + Future getPostBySlug(String slug) async => + Post.fromJson(await _repo.getPostBySlug(slug)); + + Future getAuthor(String authorSlug) async => + Author.fromJson(await _repo.getAuthor(authorSlug)); Future getPostsByAuthor( String authorId, { String afterCursor = '', - }) async { - final raw = await _repo.getPostsByAuthor(authorId, afterCursor: afterCursor); - final posts = raw.items - .map((edge) => Post.fromJson(edge['node'] as Map)) - .toList(); - return (posts: posts, endCursor: raw.endCursor, hasNextPage: raw.hasNextPage); - } + }) async => _toPostsPage( + await _repo.getPostsByAuthor(authorId, afterCursor: afterCursor), + ); Future getPostsByTag( String tagSlug, { String afterCursor = '', - }) async { - final raw = await _repo.getPostsByTag(tagSlug, afterCursor: afterCursor); - final posts = raw.items - .map((edge) => Post.fromJson(edge['node'] as Map)) - .toList(); - return (posts: posts, endCursor: raw.endCursor, hasNextPage: raw.hasNextPage); - } + }) async => _toPostsPage( + await _repo.getPostsByTag(tagSlug, afterCursor: afterCursor), + ); } diff --git a/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart b/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart index 59ca6587e..ec0a71399 100644 --- a/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart +++ b/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart @@ -4,8 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/news/models/author_model.dart'; +import 'package:mobile_app_new/news/controllers/feed_controller.dart'; import 'package:mobile_app_new/news/ui/author_feed/author_feed_viewmodel.dart'; -import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_feed_list.dart'; import 'package:mobile_app_new/widgets/error_retry.dart'; class NewsAuthorFeedView extends ConsumerWidget { @@ -38,7 +39,7 @@ class NewsAuthorFeedView extends ConsumerWidget { } Widget _buildFeed(Author author) => PostFeedList( - authorId: author.id, + source: NewsFeedSource.author(author.id), header: _AuthorDetails(author: author), ); } diff --git a/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.dart b/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.dart index 4bb5ab281..624cd6eaf 100644 --- a/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.dart +++ b/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.dart @@ -7,5 +7,5 @@ part 'author_feed_viewmodel.g.dart'; // Only used on a cold deep link; in-app navigation hands the author over as extra @riverpod Future newsAuthor(Ref ref, String username) { - return ref.read(newsApiServiceProvider).getAuthor(username); + return ref.watch(newsApiServiceProvider).getAuthor(username); } diff --git a/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.g.dart b/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.g.dart index 022778cbe..3738ed4e3 100644 --- a/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.g.dart +++ b/mobile-app-new/lib/news/ui/author_feed/author_feed_viewmodel.g.dart @@ -58,7 +58,7 @@ final class NewsAuthorProvider } } -String _$newsAuthorHash() => r'bdfbffd921588b7ac283c6fa2d79a2453420a862'; +String _$newsAuthorHash() => r'6393ad5923b855669e36c0e372aeb246477dce23'; final class NewsAuthorFamily extends $Family with $FunctionalFamilyOverride, String> { diff --git a/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_view.dart b/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_view.dart index 3b14af486..243d39e18 100644 --- a/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_view.dart +++ b/mobile-app-new/lib/news/ui/bookmark_feed/bookmark_feed_view.dart @@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/news/controllers/bookmarks_controller.dart'; import 'package:mobile_app_new/widgets/error_retry.dart'; class NewsBookmarkFeedView extends ConsumerWidget { diff --git a/mobile-app-new/lib/news/ui/bookmark_post/bookmark_post_view.dart b/mobile-app-new/lib/news/ui/bookmark_post/bookmark_post_view.dart index 0399e1d12..335a87258 100644 --- a/mobile-app-new/lib/news/ui/bookmark_post/bookmark_post_view.dart +++ b/mobile-app-new/lib/news/ui/bookmark_post/bookmark_post_view.dart @@ -4,7 +4,7 @@ import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; import 'package:mobile_app_new/news/ui/widgets/back_to_top_button.dart'; -import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/news/controllers/bookmarks_controller.dart'; // Reads the HTML stored at bookmark time, so it works with no network. class NewsBookmarkPostView extends ConsumerStatefulWidget { @@ -20,37 +20,15 @@ class NewsBookmarkPostView extends ConsumerStatefulWidget { class _NewsBookmarkPostViewState extends ConsumerState { final ScrollController _scrollController = ScrollController(); - bool _showToTopButton = false; - // NOTE: Parsed and stored here to avoid re-parsing on every build List? _htmlWidgets; - @override - void initState() { - super.initState(); - _scrollController.addListener(_handleToTopButtonVisibility); - } - @override void dispose() { _scrollController.dispose(); super.dispose(); } - void _handleToTopButtonVisibility() { - final shouldShow = _scrollController.offset >= 100; - if (shouldShow == _showToTopButton) return; - setState(() => _showToTopButton = shouldShow); - } - - void _goToTop() { - _scrollController.animateTo( - 0, - duration: const Duration(milliseconds: 1000), - curve: Curves.easeInOut, - ); - } - @override Widget build(BuildContext context) { final post = widget.post; @@ -67,9 +45,7 @@ class _NewsBookmarkPostViewState extends ConsumerState { return Scaffold( backgroundColor: FccColors.gray90, - floatingActionButton: _showToTopButton - ? BackToTopButton(onPressed: _goToTop) - : null, + floatingActionButton: BackToTopButton(controller: _scrollController), body: SafeArea( child: CustomScrollView( controller: _scrollController, diff --git a/mobile-app-new/lib/news/ui/feed/feed_view.dart b/mobile-app-new/lib/news/ui/feed/feed_view.dart index d62625be9..a2992f185 100644 --- a/mobile-app-new/lib/news/ui/feed/feed_view.dart +++ b/mobile-app-new/lib/news/ui/feed/feed_view.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list.dart'; +import 'package:mobile_app_new/news/controllers/feed_controller.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_feed_list.dart'; class NewsFeedView extends StatelessWidget { const NewsFeedView({super.key}); @override - Widget build(BuildContext context) => const PostFeedList(); + Widget build(BuildContext context) => + const PostFeedList(source: NewsFeedSource.all()); } diff --git a/mobile-app-new/lib/news/ui/post/post_view.dart b/mobile-app-new/lib/news/ui/post/post_view.dart index af67c3da2..bf553753d 100644 --- a/mobile-app-new/lib/news/ui/post/post_view.dart +++ b/mobile-app-new/lib/news/ui/post/post_view.dart @@ -6,15 +6,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/news/models/post_model.dart'; -import 'package:mobile_app_new/routing/news.dart'; -import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; import 'package:mobile_app_new/news/ui/post/post_viewmodel.dart'; import 'package:mobile_app_new/news/ui/widgets/back_to_top_button.dart'; import 'package:mobile_app_new/news/ui/widgets/bookmark_button.dart'; import 'package:mobile_app_new/news/ui/widgets/bottom_button.dart'; import 'package:mobile_app_new/news/ui/widgets/tag_button.dart'; -import 'package:share_plus/share_plus.dart'; +import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/widgets/error_retry.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; +import 'package:share_plus/share_plus.dart'; class NewsPostHeader extends StatelessWidget { const NewsPostHeader({super.key, required this.post}); @@ -71,11 +71,8 @@ class NewsPostHeader extends StatelessWidget { if (post.tags.isNotEmpty) Wrap( children: [ - for (int j = 0; j < post.tags.length && j < 3; j++) - TagButton( - tagName: post.tags[j].name, - tagSlug: post.tags[j].slug, - ), + for (final tag in post.tags.take(3)) + TagButton(tagName: tag.name, tagSlug: tag.slug), ], ), ], @@ -100,12 +97,10 @@ class NewsPostView extends ConsumerStatefulWidget { class _NewsPostViewState extends ConsumerState { final ScrollController _scrollController = ScrollController(); - final ScrollController _bottomButtonController = ScrollController(); final GlobalKey _shareButtonKey = GlobalKey(); - bool _hasInitializedAnimation = false; - bool _showToTopButton = false; + bool _showBottomBar = true; // NOTE: Parsed and stored here to avoid re-parsing on every build List? _htmlWidgets; @@ -113,67 +108,25 @@ class _NewsPostViewState extends ConsumerState { @override void initState() { super.initState(); - _scrollController.addListener(_handleBottomButtonAnimation); - _scrollController.addListener(_handleToTopButtonVisibility); + _scrollController.addListener(_handleBottomBarVisibility); } @override void dispose() { _scrollController.dispose(); - _bottomButtonController.dispose(); super.dispose(); } double _lastScrollOffset = 0; - void _handleBottomButtonAnimation() { - if (!_bottomButtonController.hasClients) return; + void _handleBottomBarVisibility() { + final offset = _scrollController.offset; + // Scrolling up reveals the bar, scrolling down hides it. + final shouldShow = offset <= _lastScrollOffset; + _lastScrollOffset = offset; - if (_scrollController.offset <= _lastScrollOffset) { - // Scrolling up — show buttons - _bottomButtonController.animateTo( - _bottomButtonController.position.maxScrollExtent - 50, - duration: const Duration(milliseconds: 1000), - curve: Curves.easeInOut, - ); - } else { - // Scrolling down — hide buttons - _bottomButtonController.animateTo( - 0, - duration: const Duration(milliseconds: 1000), - curve: Curves.easeInOut, - ); - } - _lastScrollOffset = _scrollController.offset; - } - - void _handleToTopButtonVisibility() { - final shouldShow = _scrollController.offset >= 100; - if (shouldShow == _showToTopButton) return; - setState(() => _showToTopButton = shouldShow); - } - - void _goToTop() { - _scrollController.animateTo( - 0, - duration: const Duration(milliseconds: 1000), - curve: Curves.easeInOut, - ); - } - - void _initBottomButtonAnimation() { - if (_hasInitializedAnimation) return; - _hasInitializedAnimation = true; - - Future.delayed(const Duration(seconds: 1), () { - if (_bottomButtonController.hasClients) { - _bottomButtonController.animateTo( - _bottomButtonController.position.maxScrollExtent - 50, - duration: const Duration(milliseconds: 1000), - curve: Curves.easeInOut, - ); - } - }); + if (shouldShow == _showBottomBar) return; + setState(() => _showBottomBar = shouldShow); } @override @@ -182,9 +135,7 @@ class _NewsPostViewState extends ConsumerState { return Scaffold( backgroundColor: FccColors.gray90, - floatingActionButton: _showToTopButton - ? BackToTopButton(onPressed: _goToTop) - : null, + floatingActionButton: BackToTopButton(controller: _scrollController), body: postAsync.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) { @@ -196,7 +147,6 @@ class _NewsPostViewState extends ConsumerState { }, data: (post) { final htmlWidgets = _htmlWidgets ??= _parseHtml(post); - _initBottomButtonAnimation(); return Stack( children: [ @@ -268,16 +218,16 @@ class _NewsPostViewState extends ConsumerState { Widget _buildBottomButtons(Post post) { return Align( alignment: Alignment.bottomCenter, - child: SizedBox( - height: 75, - width: 300, - child: ListView( - physics: const NeverScrollableScrollPhysics(), - controller: _bottomButtonController, - children: [ - Row( + child: AnimatedSlide( + offset: _showBottomBar ? Offset.zero : const Offset(0, 1), + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + child: Padding( + padding: const EdgeInsets.only(bottom: 32), + child: SizedBox( + width: 300, + child: Row( children: [ - Container(height: 150), BookmarkButton(post: post), const SizedBox( height: 35, @@ -292,7 +242,7 @@ class _NewsPostViewState extends ConsumerState { ), ], ), - ], + ), ), ), ); diff --git a/mobile-app-new/lib/news/ui/post/post_viewmodel.dart b/mobile-app-new/lib/news/ui/post/post_viewmodel.dart index 7a5f7bc78..abb0a7260 100644 --- a/mobile-app-new/lib/news/ui/post/post_viewmodel.dart +++ b/mobile-app-new/lib/news/ui/post/post_viewmodel.dart @@ -6,5 +6,5 @@ part 'post_viewmodel.g.dart'; @riverpod Future newsPost(Ref ref, String slug) { - return ref.read(newsApiServiceProvider).getPostBySlug(slug); + return ref.watch(newsApiServiceProvider).getPostBySlug(slug); } diff --git a/mobile-app-new/lib/news/ui/post/post_viewmodel.g.dart b/mobile-app-new/lib/news/ui/post/post_viewmodel.g.dart index 5e0656569..6cf06e6d2 100644 --- a/mobile-app-new/lib/news/ui/post/post_viewmodel.g.dart +++ b/mobile-app-new/lib/news/ui/post/post_viewmodel.g.dart @@ -58,7 +58,7 @@ final class NewsPostProvider } } -String _$newsPostHash() => r'26d73110a1b22400e8030d5543a43f78104a83f3'; +String _$newsPostHash() => r'f7390a9239ff0a618776dd692722c37adc28b846'; final class NewsPostFamily extends $Family with $FunctionalFamilyOverride, String> { diff --git a/mobile-app-new/lib/news/ui/search/search_view.dart b/mobile-app-new/lib/news/ui/search/search_view.dart index 0a2140bd9..b99f6aca8 100644 --- a/mobile-app-new/lib/news/ui/search/search_view.dart +++ b/mobile-app-new/lib/news/ui/search/search_view.dart @@ -17,21 +17,19 @@ class NewsSearchView extends ConsumerStatefulWidget { class _NewsSearchViewState extends ConsumerState { final TextEditingController _searchBarController = TextEditingController(); - String _searchTerm = ''; - @override void dispose() { _searchBarController.dispose(); super.dispose(); } - void _onSearchTermChanged(String term) { - setState(() => _searchTerm = term); - ref.read(newsSearchProvider.notifier).search(term); - } + void _onSearchTermChanged(String term) => + ref.read(newsSearchProvider.notifier).search(term); @override Widget build(BuildContext context) { + final results = ref.watch(newsSearchProvider); + return Column( children: [ TextField( @@ -46,33 +44,34 @@ class _NewsSearchViewState extends ConsumerState { ), ), const Divider(color: FccColors.gray90, thickness: 4, height: 4), - Expanded(child: _buildResults()), + Expanded( + child: ValueListenableBuilder( + valueListenable: _searchBarController, + builder: (context, value, _) => value.text.isEmpty + ? const Center(child: Text('Search for tutorials')) + : _buildResults(results), + ), + ), ], ); } - Widget _buildResults() { - if (_searchTerm.isEmpty) { - return const Center(child: Text('Search for tutorials')); - } - - return ref - .watch(newsSearchProvider) - .when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stackTrace) { - log('Error loading tutorials: $error\n$stackTrace'); - return const Center( - child: Text( - 'There was an error loading tutorials \n please try again', - textAlign: TextAlign.center, - ), - ); - }, - data: (posts) => posts.isEmpty - ? const Center(child: Text('No Tutorials Found')) - : _buildResultsList(posts), + Widget _buildResults(AsyncValue> results) { + return results.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) { + log('Error loading tutorials: $error\n$stackTrace'); + return const Center( + child: Text( + 'There was an error loading tutorials \n please try again', + textAlign: TextAlign.center, + ), ); + }, + data: (posts) => posts.isEmpty + ? const Center(child: Text('No Tutorials Found')) + : _buildResultsList(posts), + ); } Widget _buildResultsList(List posts) { diff --git a/mobile-app-new/lib/news/ui/tag_feed/tag_feed_view.dart b/mobile-app-new/lib/news/ui/tag_feed/tag_feed_view.dart index 2faf97e89..6c36ff4ca 100644 --- a/mobile-app-new/lib/news/ui/tag_feed/tag_feed_view.dart +++ b/mobile-app-new/lib/news/ui/tag_feed/tag_feed_view.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list.dart'; +import 'package:mobile_app_new/news/controllers/feed_controller.dart'; +import 'package:mobile_app_new/news/ui/widgets/post_feed_list.dart'; class NewsTagFeedView extends StatelessWidget { const NewsTagFeedView({super.key, required this.tagSlug, this.tagName}); @@ -14,7 +15,7 @@ class NewsTagFeedView extends StatelessWidget { return Scaffold( appBar: AppBar(title: Text('Tutorials about ${tagName ?? tagSlug}')), backgroundColor: FccColors.gray90, - body: PostFeedList(tagSlug: tagSlug), + body: PostFeedList(source: NewsFeedSource.tag(tagSlug)), ); } } diff --git a/mobile-app-new/lib/news/ui/widgets/back_to_top_button.dart b/mobile-app-new/lib/news/ui/widgets/back_to_top_button.dart index 901652640..a9706350b 100644 --- a/mobile-app-new/lib/news/ui/widgets/back_to_top_button.dart +++ b/mobile-app-new/lib/news/ui/widgets/back_to_top_button.dart @@ -1,27 +1,72 @@ import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -class BackToTopButton extends StatelessWidget { - const BackToTopButton({super.key, required this.onPressed}); +class BackToTopButton extends StatefulWidget { + const BackToTopButton({ + super.key, + required this.controller, + this.showAfter = 100, + }); - final VoidCallback onPressed; + final ScrollController controller; + final double showAfter; + + @override + State createState() => _BackToTopButtonState(); +} + +class _BackToTopButtonState extends State { + bool _visible = false; + + @override + void initState() { + super.initState(); + widget.controller.addListener(_handleScroll); + } + + @override + void dispose() { + widget.controller.removeListener(_handleScroll); + super.dispose(); + } + + void _handleScroll() { + if (!widget.controller.hasClients) return; + + final shouldShow = widget.controller.offset >= widget.showAfter; + if (shouldShow == _visible) return; + setState(() => _visible = shouldShow); + } + + void _goToTop() { + widget.controller.animateTo( + 0, + duration: const Duration(milliseconds: 1000), + curve: Curves.easeInOut, + ); + } @override Widget build(BuildContext context) { - return Padding( - // Lifts the button clear of the bottom action buttons. - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 64), - child: FloatingActionButton( - onPressed: onPressed, - shape: RoundedRectangleBorder( - side: const BorderSide(width: 1, color: Colors.white), - borderRadius: BorderRadius.circular(100), - ), - backgroundColor: FccColors.gray80, - child: const Icon( - Icons.keyboard_arrow_up, - size: 40, - color: Colors.white, + return AnimatedScale( + scale: _visible ? 1 : 0, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + child: Padding( + // Lifts the button clear of the bottom action buttons. + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 64), + child: FloatingActionButton( + onPressed: _goToTop, + shape: RoundedRectangleBorder( + side: const BorderSide(width: 1, color: Colors.white), + borderRadius: BorderRadius.circular(100), + ), + backgroundColor: FccColors.gray80, + child: const Icon( + Icons.keyboard_arrow_up, + size: 40, + color: Colors.white, + ), ), ), ); diff --git a/mobile-app-new/lib/news/ui/widgets/bookmark_button.dart b/mobile-app-new/lib/news/ui/widgets/bookmark_button.dart index 9d51d2b2c..14cfa8de0 100644 --- a/mobile-app-new/lib/news/ui/widgets/bookmark_button.dart +++ b/mobile-app-new/lib/news/ui/widgets/bookmark_button.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/news/models/bookmarked_post_model.dart'; import 'package:mobile_app_new/news/models/post_model.dart'; -import 'package:mobile_app_new/news/ui/bookmark_feed/bookmark_feed_viewmodel.dart'; +import 'package:mobile_app_new/news/controllers/bookmarks_controller.dart'; import 'package:mobile_app_new/news/ui/widgets/bottom_button.dart'; class BookmarkButton extends ConsumerWidget { diff --git a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list.dart b/mobile-app-new/lib/news/ui/widgets/post_feed_list.dart similarity index 90% rename from mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list.dart rename to mobile-app-new/lib/news/ui/widgets/post_feed_list.dart index 967cb1d6d..9b7c6a9c5 100644 --- a/mobile-app-new/lib/news/ui/widgets/post_feed_list/post_feed_list.dart +++ b/mobile-app-new/lib/news/ui/widgets/post_feed_list.dart @@ -2,20 +2,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/news/ui/widgets/post_feed_list/post_feed_list_state.dart'; +import 'package:mobile_app_new/news/controllers/feed_controller.dart'; import 'package:mobile_app_new/news/ui/widgets/post_tile.dart'; import 'package:mobile_app_new/widgets/error_retry.dart'; class PostFeedList extends ConsumerStatefulWidget { - const PostFeedList({ - super.key, - this.tagSlug = '', - this.authorId = '', - this.header, - }); + const PostFeedList({super.key, required this.source, this.header}); - final String tagSlug; - final String authorId; + final NewsFeedSource source; // NOTE: The header is optional and can be used to display a widget above the // list of posts, such as author details or tag information. @@ -28,8 +22,7 @@ class PostFeedList extends ConsumerStatefulWidget { class _PostFeedListState extends ConsumerState { final _scrollController = ScrollController(); - NewsFeedNotifierProvider get _provider => - newsFeedProvider(tagSlug: widget.tagSlug, authorId: widget.authorId); + NewsFeedNotifierProvider get _provider => newsFeedProvider(widget.source); @override void initState() { diff --git a/mobile-app-new/lib/news/ui/widgets/post_tile.dart b/mobile-app-new/lib/news/ui/widgets/post_tile.dart index 141b98b73..41c4197f9 100644 --- a/mobile-app-new/lib/news/ui/widgets/post_tile.dart +++ b/mobile-app-new/lib/news/ui/widgets/post_tile.dart @@ -3,7 +3,7 @@ import 'dart:developer'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; -import 'package:mobile_app_new/news/models/post_model.dart'; +import 'package:mobile_app_new/news/models/post_summary_model.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/utils/date_utils.dart'; import 'package:mobile_app_new/news/ui/widgets/tag_button.dart'; @@ -11,7 +11,7 @@ import 'package:mobile_app_new/news/ui/widgets/tag_button.dart'; class PostTile extends StatelessWidget { const PostTile({super.key, required this.post}); - final Post post; + final PostSummary post; @override Widget build(BuildContext context) { @@ -34,10 +34,10 @@ class PostTile extends StatelessWidget { spacing: 0, runSpacing: 4, children: [ - for (int i = 0; i < post.tags.length && i < 3; i++) + for (final tag in post.tags.take(3)) TagButton( - tagName: post.tags[i].name, - tagSlug: post.tags[i].slug, + tagName: tag.name, + tagSlug: tag.slug, compact: true, ), ], From d8a963353e92e55566aebefe59ffdbc9fa872c7d Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Tue, 8 Sep 2026 00:38:59 +0530 Subject: [PATCH 28/52] feat: code radio --- .../android/app/src/main/AndroidManifest.xml | 24 +- .../res/drawable-hdpi/notification_icon.png | Bin 0 -> 759 bytes .../res/drawable-mdpi/notification_icon.png | Bin 0 -> 479 bytes .../res/drawable-xhdpi/notification_icon.png | Bin 0 -> 1080 bytes .../res/drawable-xxhdpi/notification_icon.png | Bin 0 -> 1691 bytes .../drawable-xxxhdpi/notification_icon.png | Bin 0 -> 2300 bytes mobile-app-new/ios/Runner/Info.plist | 4 + .../code_radio/models/code_radio_model.dart | 68 + .../models/code_radio_model.freezed.dart | 1727 +++++++++++++++++ .../code_radio/models/code_radio_model.g.dart | 72 + .../repositories/websocket_repository.dart | 44 + .../repositories/websocket_repository.g.dart | 59 + .../services/code_radio_service.dart | 29 + .../services/code_radio_service.g.dart | 56 + .../lib/code_radio/ui/player/player_view.dart | 224 +++ .../ui/player/player_viewmodel.dart | 58 + .../ui/player/player_viewmodel.g.dart | 131 ++ mobile-app-new/lib/main.dart | 10 +- mobile-app-new/lib/routing/code_radio.dart | 16 + mobile-app-new/lib/routing/code_radio.g.dart | 36 + mobile-app-new/lib/routing/router.dart | 7 +- .../lib/services/audio_service.dart | 144 ++ .../lib/services/audio_service.g.dart | 57 + mobile-app-new/lib/widgets/drawer/drawer.dart | 9 + mobile-app-new/pubspec.lock | 66 +- mobile-app-new/pubspec.yaml | 3 + 26 files changed, 2839 insertions(+), 5 deletions(-) create mode 100644 mobile-app-new/android/app/src/main/res/drawable-hdpi/notification_icon.png create mode 100644 mobile-app-new/android/app/src/main/res/drawable-mdpi/notification_icon.png create mode 100644 mobile-app-new/android/app/src/main/res/drawable-xhdpi/notification_icon.png create mode 100644 mobile-app-new/android/app/src/main/res/drawable-xxhdpi/notification_icon.png create mode 100644 mobile-app-new/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png create mode 100644 mobile-app-new/lib/code_radio/models/code_radio_model.dart create mode 100644 mobile-app-new/lib/code_radio/models/code_radio_model.freezed.dart create mode 100644 mobile-app-new/lib/code_radio/models/code_radio_model.g.dart create mode 100644 mobile-app-new/lib/code_radio/repositories/websocket_repository.dart create mode 100644 mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart create mode 100644 mobile-app-new/lib/code_radio/services/code_radio_service.dart create mode 100644 mobile-app-new/lib/code_radio/services/code_radio_service.g.dart create mode 100644 mobile-app-new/lib/code_radio/ui/player/player_view.dart create mode 100644 mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart create mode 100644 mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart create mode 100644 mobile-app-new/lib/routing/code_radio.dart create mode 100644 mobile-app-new/lib/routing/code_radio.g.dart create mode 100644 mobile-app-new/lib/services/audio_service.dart create mode 100644 mobile-app-new/lib/services/audio_service.g.dart diff --git a/mobile-app-new/android/app/src/main/AndroidManifest.xml b/mobile-app-new/android/app/src/main/AndroidManifest.xml index d94a739fe..7780a74f4 100644 --- a/mobile-app-new/android/app/src/main/AndroidManifest.xml +++ b/mobile-app-new/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,15 @@ - + + + + + + + + + + + + + + + 2(gre?vgR+FEFCJQT)gt3sHs3}qsp)_l=&}e2Qvzm>jW+r9C!onD#G$9-L zD=d&~EaZQ|Mqvyd_cPCP#yR)A=Vcnp_o_S1x$g6v@B7SmUTU?9ii!$DMCKtUkj?C- zgU<80^Vwzb7xNeyL)Nip&{@E1InZ5;d`F(>IqWBa(oEzU@)Oz3z6kVski)#51KnLd z|GJ*XJ^_UzVsx_0f$p&HImj*tx*fi!gWUv>O~@DIE;56C5n`X{2Os_oS;n0rbUt$1 z&ud~o4n&_1Ud=8Cx-GsZFv6W2=&tfTee6^=AfJ)@?8-p@e0U|!FJv+Ia-e%p?A!(x zn|xqOf7#0uZj#p0BU#zbU5Z@c#c z55)eul&M?+b9~Priy>qzSp^?TFRa+de+C&ro_p_D z+=+pkg}f6xw~oCQj{1=F#lW{A1H2y4`N%8elb*|d0?0%9lEFDC`WS2DorTC*UJvL#q+j@f>A2VVN*ntG zFJJWXarObYC6f7fvDmF$=oFpn^Yxs=2#jb06tB?6aWUDE7aD p7IxDC^Va*^e|m^lR8;(Lego0u6|Q5Emc#%6002ovPDHLkV1mnheO~|o literal 0 HcmV?d00001 diff --git a/mobile-app-new/android/app/src/main/res/drawable-mdpi/notification_icon.png b/mobile-app-new/android/app/src/main/res/drawable-mdpi/notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c45718dae417fda7a3dcd78099751acec69808 GIT binary patch literal 479 zcmV<50U-W~P)A z$mSyaG03;R2tN~6VN<$nE(61G0%4m4BD;XSUv3}^j$yV;7_gC&@<^CJ8fIMT)zbgP-bm%G39 z<-2}8>DQuX$V#xU3%0c(2RN-}0>9iBy+O72=DZiED#5;KnAJubTsd=atjm1Krt%u$ z8XjHjwJkP#L)d#Gx VAPSi1GmQWM002ovPDHLkV1g<_)9L^K literal 0 HcmV?d00001 diff --git a/mobile-app-new/android/app/src/main/res/drawable-xhdpi/notification_icon.png b/mobile-app-new/android/app/src/main/res/drawable-xhdpi/notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7ff7f93368aa2d7a9b4ef5f65a3d48337ea236d0 GIT binary patch literal 1080 zcmV-81jqY{P)zi47?X|z6qKu4;jEs!`84)Rm z)^S1=OGY z5KyjyozKD*hE1;L5X4=b9@ekhV(0?GrZCuc%H-Ar-wQB7k17ZB(m z`GJqJ9|~$Kq3_lwVh`s+LG5Adp>ylNeh!$^E%*(pWl26WNXctB1#u^WXmc6Qz6R>dY-KMLagYBrzCa~h7Yb_c zTc0}i)Ld=mdG)2Csvzdq4D}=Zk_o~(VoPesm z-!EBylKjEL4K)X?&o=hd+-~Lp_6mWTRaWcs2^u2q*`ha|vK8tGH9^<)XYQ2-^82mN z4))Y+H1iO9X&^rk`e?O2^Gw=S+{sv}Wlv4L zu@qgqzdtBqKyIwn_-v=la1IbN{CpC~brm-^WitEGUn)9pOkh6_lZjZlbc|8ag z{Ll(=YpmupdvPGQ!1B6$(vRc_9JTNrs0;fHGRrLA!d?={F1N{yVUH2F)7`c);*Zik z;{M&y-M*fEM%@hl~jjiZh3Vz^UKtYA% z@BCNLaLzE|)@w0ej}Z5TRiYn(%m^D((K%FbCJ1p4q3HegIQ#7d#yVzW>ULhmew#pp y-s0Z0CbHLFV5}N(H}Btlxto!Zk&%({uk#aK&ztK7eT(h@0000oW^Ulow?U~u1{Lp@25&Y|2@7nV}tLNXN{%j(|qzBfb{L&xpfV(^J6Mh9+ZF z_98A|O;FIF3-MRdP=XI)!Rw9%OSApvj779CvTU&M>jC=uq z>=6ohZ!>*f=X?fn_n&loKv~I}5Qtm&RzaUK?e7mYknF@!*PG>ikQ`&gO7Q!SxILOr zs82gO`2)?QUSn9(0?9K-9ArDg0?&J?2eX6!0KSn9Oc)jT9!I`%6!(eK=Lvp+|T(Yz;6iRedAxmm5Fu> z{ry4XvmUYHfOB)it@~20;Cu^B8sU$eZ-Qi}_C>sj=#ywyQ|`YNx46qeA0oQp73}ht z>>0b5^DV^f{-^BYd|lvmx@1pdM)#Sf7=0Jz_0k+H*;V*<+oK8a`oZ`v;(QAvFEm13 zr!>CS)kCs#t0Fd0&E=GTSMpCjon%h1b#)LoKD#Enh&3&coN9zRfT66Z3Ovp+o<)3! zIFxFZvZ#cIY+W71JrwMW{%qE?K=MQ*R8C+`Rp9Xyd0OXhQiVj0W0z6z*cWxV!sJ_kh4n}V&JuL9~<7%%1~)~6u%8jHK(HmYp@T zJ2;<1vb%|THr0NTq4o~rWzqNKnH#eOxu?0H8*0BI688kNSIph4$szeoq)DvtMapls zYgubS{yHw`hHJRq7jb)1mh)JXL)?VyCsWjZ6R8B$US)hzbW%8)wHD+DA$Cz+8PmAl z7u0SrK6=c9SyPB)*Ft(Z3}THBsGDefQeI(Q5zq}pT#MK)?)8sO7q%K7Mb}WZ7S!Sv zsb3neEvzX8;yI>JsrB{|b$!IG&@H#K&KGg_44qY{JkA6mc*1>^6l?;QF)92h=^5c+|ecPBT*jbU)`F&iNqj?9pwLF3$O)V>id4JiwaDh?^I^O~yB~&IfVZu8&B&1z`cGStbp> zW7IL*0}yxn@SFFv14d&d`}vxln%A?=2XXhd3Ho2ol>zN2#F_keUr>8PGUgOLmRk4I zQp9Zw*qt|RxRiCiNQIBFq~;VuLGAKntSNd7<(pznfbjy!e(a<(*&(d!DTrIL*f$nB z_Dfh76mg5$bOo$a^;%Q?gMqjSVfrbLE}0r_0D6N)M~K^45BBP%Q;7SJ{8Bks|5hUs li9{liNF)-8L?V$+^Dh$1qlZ4Jz9|3z002ovPDHLkV1o2pIm`e6 literal 0 HcmV?d00001 diff --git a/mobile-app-new/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png b/mobile-app-new/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e9db5cbd42c6111d613058fc7d4d35d9a9aeb45d GIT binary patch literal 2300 zcmVa~f&3hhpN3(ouYL8xY?!#1r; z`ygxM+q6AZGdirqJhTgF|II3vbEIl#I&9Ms(njN#w0TuK3hr!>@JA`=YoIw%HL^QsXjD}aSA5z&11|hXrE4Lv2!_XF4aea!A{Z|kk4t` zt2PFV^EB-NOyGsjm|MfYXv!(7jRFJhK>Lpbx0}DCYGc7z&t!3*pe>=-W5bx+(LT!? zpJDf8XVnIU0al}ZD1rY?+e5W6VZ7fL;{OVh`4D!=+Fs;i+FGg&0v#^^Z>(~HYGVQ) zi~mdN@TC1;wXtE$4Pzc7^(b<>>SM#0i_m^6^{^*&W7YP8j^?I4Dgo?;v$bl+ zfN}4Wx)iZK`8!%BHjKGt7Q2Yuv$<8<8F-pOxk`0oz_^Q{Jo->xqb;OmW5bx2YDLKI zs_O(i!S<>+{VQz+)s6wFSA{g=%u<#?|>~EM*o`-SQMtK4_9Ve-e*ub9xyGKPHQC%zG4c08@<*FMC z#=c1E(!?7Wd01*N=4Db3yN7G5t`Sr}PMfK24B+3asw7wwTd1$tFy>}j338h18eyU5 z#fgnBh7YTHz_@2hUD`5_jyxfK2ILvBbhK9F&*(Dx2ngezCUyA- z+KeJ8T38Aiho~+ljCX}rpa#o8u0?xHFJwHWzQ=;Gzps@cOR25|MJs4249RD7gt4|q zc?Mp`HCdUX^g_mL@x1|U>Ei!l!8kihU5r&#R|3B7t9(y&F@W#Ac~67aV2||5SvkA0 zRUA|BZ7=nwXm{A(Uqy`reD{R1uIfs_b3n>As*3^RT&IDj;b(<%^P+mI$d(07F|)gW z{{L85>_WVpRv&=%sY>)!Cir%jwq1|yL!GiG2%dZ^VoRtCXV;dby;HbmyZ06=mJbd6 zZ&iqw)4XGXqLsB2BF}>9e?i)t8gv>;dyr5b4cQRXc2KMe-#?^uPmpzh{=cgMk#(8V z0{^xsJE<-t^nbhtoQ4~8m?yJ&d>Ugy7v zz73D7uTaqU2@=bmxOSs23SW`Jm#QWdC~tYG!?uA}HUQq&Yx=wm_M|VT{z3phH&Nsj z)zm`qy;Ar=)r5e4mq&SA+#+x9nYN}1OWkTPAk zQvD4DK9twd*Ac3#h2l-5uo3)F8hy^zAQkMZ4=?6Nc5@z9T_Y@XqBt=&SKSa$y`eS7_5rn?sZ#r6bO4^JZ7}ek8qlHPC8`?&{T(j;jUTD5HSiTGU7&xLwxDYJLSMfS|Hccd3jzFC zRgQI48>pr)@I#FH^0igFWmVe=lxM}p@v7?!eXWl2CZ^zHa|2UPdETPehd_TnEcni8 zKl`nNPF8_`l0|HUvZ8AH0)KfH3Hq|n!B^d#wP$s`-WPaIOHaqxJSt4~0OhTU#_g)> z3waGWNWj005L7x)rho*BJfuMY+O10Tkn z2soj%dyx4pVjIGOjTYxXhr6LXimKpUT}j(f@dq6cYL*kzRrP zwUHT@An=FX8VgK23+3+UDAmq%*rr3JjmEi{)Xgsf-`!xtJj0d|gAGgnUx3fnZ60A* zTSxtwwnpHoWn08)-hMYjfp3Ff69Lx{5{X12kw_#Gi9{liNF)-8L?V$$Boc|QH~#}` WBP#H`*b7tu0000 UIApplicationSupportsIndirectInputEvents + UIBackgroundModes + + audio + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile diff --git a/mobile-app-new/lib/code_radio/models/code_radio_model.dart b/mobile-app-new/lib/code_radio/models/code_radio_model.dart new file mode 100644 index 000000000..00a63f327 --- /dev/null +++ b/mobile-app-new/lib/code_radio/models/code_radio_model.dart @@ -0,0 +1,68 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'code_radio_model.freezed.dart'; +part 'code_radio_model.g.dart'; + +@freezed +abstract class CodeRadio with _$CodeRadio { + const factory CodeRadio({ + required Station station, + required Listeners listeners, + @JsonKey(name: 'now_playing') required NowPlaying nowPlaying, + @JsonKey(name: 'playing_next') required PlayingNext playingNext, + }) = _CodeRadio; + + factory CodeRadio.fromJson(Map json) => + _$CodeRadioFromJson(json); +} + +@freezed +abstract class Station with _$Station { + const factory Station({ + @JsonKey(name: 'listen_url') required String listenUrl, + }) = _Station; + + factory Station.fromJson(Map json) => + _$StationFromJson(json); +} + +@freezed +abstract class Listeners with _$Listeners { + const factory Listeners({required int total}) = _Listeners; + + factory Listeners.fromJson(Map json) => + _$ListenersFromJson(json); +} + +@freezed +abstract class NowPlaying with _$NowPlaying { + const factory NowPlaying({ + required int duration, + required int elapsed, + required Song song, + }) = _NowPlaying; + + factory NowPlaying.fromJson(Map json) => + _$NowPlayingFromJson(json); +} + +@freezed +abstract class PlayingNext with _$PlayingNext { + const factory PlayingNext({required Song song}) = _PlayingNext; + + factory PlayingNext.fromJson(Map json) => + _$PlayingNextFromJson(json); +} + +@freezed +abstract class Song with _$Song { + const factory Song({ + required String id, + required String title, + required String artist, + required String album, + required String art, + }) = _Song; + + factory Song.fromJson(Map json) => _$SongFromJson(json); +} diff --git a/mobile-app-new/lib/code_radio/models/code_radio_model.freezed.dart b/mobile-app-new/lib/code_radio/models/code_radio_model.freezed.dart new file mode 100644 index 000000000..1cbe5e844 --- /dev/null +++ b/mobile-app-new/lib/code_radio/models/code_radio_model.freezed.dart @@ -0,0 +1,1727 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'code_radio_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$CodeRadio { + + Station get station; Listeners get listeners;@JsonKey(name: 'now_playing') NowPlaying get nowPlaying;@JsonKey(name: 'playing_next') PlayingNext get playingNext; +/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CodeRadioCopyWith get copyWith => _$CodeRadioCopyWithImpl(this as CodeRadio, _$identity); + + /// Serializes this CodeRadio to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CodeRadio&&(identical(other.station, station) || other.station == station)&&(identical(other.listeners, listeners) || other.listeners == listeners)&&(identical(other.nowPlaying, nowPlaying) || other.nowPlaying == nowPlaying)&&(identical(other.playingNext, playingNext) || other.playingNext == playingNext)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,station,listeners,nowPlaying,playingNext); + +@override +String toString() { + return 'CodeRadio(station: $station, listeners: $listeners, nowPlaying: $nowPlaying, playingNext: $playingNext)'; +} + + +} + +/// @nodoc +abstract mixin class $CodeRadioCopyWith<$Res> { + factory $CodeRadioCopyWith(CodeRadio value, $Res Function(CodeRadio) _then) = _$CodeRadioCopyWithImpl; +@useResult +$Res call({ + Station station, Listeners listeners,@JsonKey(name: 'now_playing') NowPlaying nowPlaying,@JsonKey(name: 'playing_next') PlayingNext playingNext +}); + + +$StationCopyWith<$Res> get station;$ListenersCopyWith<$Res> get listeners;$NowPlayingCopyWith<$Res> get nowPlaying;$PlayingNextCopyWith<$Res> get playingNext; + +} +/// @nodoc +class _$CodeRadioCopyWithImpl<$Res> + implements $CodeRadioCopyWith<$Res> { + _$CodeRadioCopyWithImpl(this._self, this._then); + + final CodeRadio _self; + final $Res Function(CodeRadio) _then; + +/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? station = null,Object? listeners = null,Object? nowPlaying = null,Object? playingNext = null,}) { + return _then(_self.copyWith( +station: null == station ? _self.station : station // ignore: cast_nullable_to_non_nullable +as Station,listeners: null == listeners ? _self.listeners : listeners // ignore: cast_nullable_to_non_nullable +as Listeners,nowPlaying: null == nowPlaying ? _self.nowPlaying : nowPlaying // ignore: cast_nullable_to_non_nullable +as NowPlaying,playingNext: null == playingNext ? _self.playingNext : playingNext // ignore: cast_nullable_to_non_nullable +as PlayingNext, + )); +} +/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$StationCopyWith<$Res> get station { + + return $StationCopyWith<$Res>(_self.station, (value) { + return _then(_self.copyWith(station: value)); + }); +}/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ListenersCopyWith<$Res> get listeners { + + return $ListenersCopyWith<$Res>(_self.listeners, (value) { + return _then(_self.copyWith(listeners: value)); + }); +}/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$NowPlayingCopyWith<$Res> get nowPlaying { + + return $NowPlayingCopyWith<$Res>(_self.nowPlaying, (value) { + return _then(_self.copyWith(nowPlaying: value)); + }); +}/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PlayingNextCopyWith<$Res> get playingNext { + + return $PlayingNextCopyWith<$Res>(_self.playingNext, (value) { + return _then(_self.copyWith(playingNext: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [CodeRadio]. +extension CodeRadioPatterns on CodeRadio { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CodeRadio value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CodeRadio() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CodeRadio value) $default,){ +final _that = this; +switch (_that) { +case _CodeRadio(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CodeRadio value)? $default,){ +final _that = this; +switch (_that) { +case _CodeRadio() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( Station station, Listeners listeners, @JsonKey(name: 'now_playing') NowPlaying nowPlaying, @JsonKey(name: 'playing_next') PlayingNext playingNext)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CodeRadio() when $default != null: +return $default(_that.station,_that.listeners,_that.nowPlaying,_that.playingNext);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( Station station, Listeners listeners, @JsonKey(name: 'now_playing') NowPlaying nowPlaying, @JsonKey(name: 'playing_next') PlayingNext playingNext) $default,) {final _that = this; +switch (_that) { +case _CodeRadio(): +return $default(_that.station,_that.listeners,_that.nowPlaying,_that.playingNext);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( Station station, Listeners listeners, @JsonKey(name: 'now_playing') NowPlaying nowPlaying, @JsonKey(name: 'playing_next') PlayingNext playingNext)? $default,) {final _that = this; +switch (_that) { +case _CodeRadio() when $default != null: +return $default(_that.station,_that.listeners,_that.nowPlaying,_that.playingNext);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CodeRadio implements CodeRadio { + const _CodeRadio({required this.station, required this.listeners, @JsonKey(name: 'now_playing') required this.nowPlaying, @JsonKey(name: 'playing_next') required this.playingNext}); + factory _CodeRadio.fromJson(Map json) => _$CodeRadioFromJson(json); + +@override final Station station; +@override final Listeners listeners; +@override@JsonKey(name: 'now_playing') final NowPlaying nowPlaying; +@override@JsonKey(name: 'playing_next') final PlayingNext playingNext; + +/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CodeRadioCopyWith<_CodeRadio> get copyWith => __$CodeRadioCopyWithImpl<_CodeRadio>(this, _$identity); + +@override +Map toJson() { + return _$CodeRadioToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CodeRadio&&(identical(other.station, station) || other.station == station)&&(identical(other.listeners, listeners) || other.listeners == listeners)&&(identical(other.nowPlaying, nowPlaying) || other.nowPlaying == nowPlaying)&&(identical(other.playingNext, playingNext) || other.playingNext == playingNext)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,station,listeners,nowPlaying,playingNext); + +@override +String toString() { + return 'CodeRadio(station: $station, listeners: $listeners, nowPlaying: $nowPlaying, playingNext: $playingNext)'; +} + + +} + +/// @nodoc +abstract mixin class _$CodeRadioCopyWith<$Res> implements $CodeRadioCopyWith<$Res> { + factory _$CodeRadioCopyWith(_CodeRadio value, $Res Function(_CodeRadio) _then) = __$CodeRadioCopyWithImpl; +@override @useResult +$Res call({ + Station station, Listeners listeners,@JsonKey(name: 'now_playing') NowPlaying nowPlaying,@JsonKey(name: 'playing_next') PlayingNext playingNext +}); + + +@override $StationCopyWith<$Res> get station;@override $ListenersCopyWith<$Res> get listeners;@override $NowPlayingCopyWith<$Res> get nowPlaying;@override $PlayingNextCopyWith<$Res> get playingNext; + +} +/// @nodoc +class __$CodeRadioCopyWithImpl<$Res> + implements _$CodeRadioCopyWith<$Res> { + __$CodeRadioCopyWithImpl(this._self, this._then); + + final _CodeRadio _self; + final $Res Function(_CodeRadio) _then; + +/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? station = null,Object? listeners = null,Object? nowPlaying = null,Object? playingNext = null,}) { + return _then(_CodeRadio( +station: null == station ? _self.station : station // ignore: cast_nullable_to_non_nullable +as Station,listeners: null == listeners ? _self.listeners : listeners // ignore: cast_nullable_to_non_nullable +as Listeners,nowPlaying: null == nowPlaying ? _self.nowPlaying : nowPlaying // ignore: cast_nullable_to_non_nullable +as NowPlaying,playingNext: null == playingNext ? _self.playingNext : playingNext // ignore: cast_nullable_to_non_nullable +as PlayingNext, + )); +} + +/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$StationCopyWith<$Res> get station { + + return $StationCopyWith<$Res>(_self.station, (value) { + return _then(_self.copyWith(station: value)); + }); +}/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ListenersCopyWith<$Res> get listeners { + + return $ListenersCopyWith<$Res>(_self.listeners, (value) { + return _then(_self.copyWith(listeners: value)); + }); +}/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$NowPlayingCopyWith<$Res> get nowPlaying { + + return $NowPlayingCopyWith<$Res>(_self.nowPlaying, (value) { + return _then(_self.copyWith(nowPlaying: value)); + }); +}/// Create a copy of CodeRadio +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PlayingNextCopyWith<$Res> get playingNext { + + return $PlayingNextCopyWith<$Res>(_self.playingNext, (value) { + return _then(_self.copyWith(playingNext: value)); + }); +} +} + + +/// @nodoc +mixin _$Station { + +@JsonKey(name: 'listen_url') String get listenUrl; +/// Create a copy of Station +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$StationCopyWith get copyWith => _$StationCopyWithImpl(this as Station, _$identity); + + /// Serializes this Station to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Station&&(identical(other.listenUrl, listenUrl) || other.listenUrl == listenUrl)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,listenUrl); + +@override +String toString() { + return 'Station(listenUrl: $listenUrl)'; +} + + +} + +/// @nodoc +abstract mixin class $StationCopyWith<$Res> { + factory $StationCopyWith(Station value, $Res Function(Station) _then) = _$StationCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: 'listen_url') String listenUrl +}); + + + + +} +/// @nodoc +class _$StationCopyWithImpl<$Res> + implements $StationCopyWith<$Res> { + _$StationCopyWithImpl(this._self, this._then); + + final Station _self; + final $Res Function(Station) _then; + +/// Create a copy of Station +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? listenUrl = null,}) { + return _then(_self.copyWith( +listenUrl: null == listenUrl ? _self.listenUrl : listenUrl // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Station]. +extension StationPatterns on Station { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Station value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Station() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Station value) $default,){ +final _that = this; +switch (_that) { +case _Station(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Station value)? $default,){ +final _that = this; +switch (_that) { +case _Station() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'listen_url') String listenUrl)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Station() when $default != null: +return $default(_that.listenUrl);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'listen_url') String listenUrl) $default,) {final _that = this; +switch (_that) { +case _Station(): +return $default(_that.listenUrl);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'listen_url') String listenUrl)? $default,) {final _that = this; +switch (_that) { +case _Station() when $default != null: +return $default(_that.listenUrl);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Station implements Station { + const _Station({@JsonKey(name: 'listen_url') required this.listenUrl}); + factory _Station.fromJson(Map json) => _$StationFromJson(json); + +@override@JsonKey(name: 'listen_url') final String listenUrl; + +/// Create a copy of Station +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$StationCopyWith<_Station> get copyWith => __$StationCopyWithImpl<_Station>(this, _$identity); + +@override +Map toJson() { + return _$StationToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Station&&(identical(other.listenUrl, listenUrl) || other.listenUrl == listenUrl)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,listenUrl); + +@override +String toString() { + return 'Station(listenUrl: $listenUrl)'; +} + + +} + +/// @nodoc +abstract mixin class _$StationCopyWith<$Res> implements $StationCopyWith<$Res> { + factory _$StationCopyWith(_Station value, $Res Function(_Station) _then) = __$StationCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: 'listen_url') String listenUrl +}); + + + + +} +/// @nodoc +class __$StationCopyWithImpl<$Res> + implements _$StationCopyWith<$Res> { + __$StationCopyWithImpl(this._self, this._then); + + final _Station _self; + final $Res Function(_Station) _then; + +/// Create a copy of Station +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? listenUrl = null,}) { + return _then(_Station( +listenUrl: null == listenUrl ? _self.listenUrl : listenUrl // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$Listeners { + + int get total; +/// Create a copy of Listeners +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ListenersCopyWith get copyWith => _$ListenersCopyWithImpl(this as Listeners, _$identity); + + /// Serializes this Listeners to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Listeners&&(identical(other.total, total) || other.total == total)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,total); + +@override +String toString() { + return 'Listeners(total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class $ListenersCopyWith<$Res> { + factory $ListenersCopyWith(Listeners value, $Res Function(Listeners) _then) = _$ListenersCopyWithImpl; +@useResult +$Res call({ + int total +}); + + + + +} +/// @nodoc +class _$ListenersCopyWithImpl<$Res> + implements $ListenersCopyWith<$Res> { + _$ListenersCopyWithImpl(this._self, this._then); + + final Listeners _self; + final $Res Function(Listeners) _then; + +/// Create a copy of Listeners +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? total = null,}) { + return _then(_self.copyWith( +total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Listeners]. +extension ListenersPatterns on Listeners { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Listeners value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Listeners() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Listeners value) $default,){ +final _that = this; +switch (_that) { +case _Listeners(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Listeners value)? $default,){ +final _that = this; +switch (_that) { +case _Listeners() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int total)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Listeners() when $default != null: +return $default(_that.total);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int total) $default,) {final _that = this; +switch (_that) { +case _Listeners(): +return $default(_that.total);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int total)? $default,) {final _that = this; +switch (_that) { +case _Listeners() when $default != null: +return $default(_that.total);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Listeners implements Listeners { + const _Listeners({required this.total}); + factory _Listeners.fromJson(Map json) => _$ListenersFromJson(json); + +@override final int total; + +/// Create a copy of Listeners +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ListenersCopyWith<_Listeners> get copyWith => __$ListenersCopyWithImpl<_Listeners>(this, _$identity); + +@override +Map toJson() { + return _$ListenersToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Listeners&&(identical(other.total, total) || other.total == total)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,total); + +@override +String toString() { + return 'Listeners(total: $total)'; +} + + +} + +/// @nodoc +abstract mixin class _$ListenersCopyWith<$Res> implements $ListenersCopyWith<$Res> { + factory _$ListenersCopyWith(_Listeners value, $Res Function(_Listeners) _then) = __$ListenersCopyWithImpl; +@override @useResult +$Res call({ + int total +}); + + + + +} +/// @nodoc +class __$ListenersCopyWithImpl<$Res> + implements _$ListenersCopyWith<$Res> { + __$ListenersCopyWithImpl(this._self, this._then); + + final _Listeners _self; + final $Res Function(_Listeners) _then; + +/// Create a copy of Listeners +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? total = null,}) { + return _then(_Listeners( +total: null == total ? _self.total : total // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + + +/// @nodoc +mixin _$NowPlaying { + + int get duration; int get elapsed; Song get song; +/// Create a copy of NowPlaying +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$NowPlayingCopyWith get copyWith => _$NowPlayingCopyWithImpl(this as NowPlaying, _$identity); + + /// Serializes this NowPlaying to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is NowPlaying&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.elapsed, elapsed) || other.elapsed == elapsed)&&(identical(other.song, song) || other.song == song)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,duration,elapsed,song); + +@override +String toString() { + return 'NowPlaying(duration: $duration, elapsed: $elapsed, song: $song)'; +} + + +} + +/// @nodoc +abstract mixin class $NowPlayingCopyWith<$Res> { + factory $NowPlayingCopyWith(NowPlaying value, $Res Function(NowPlaying) _then) = _$NowPlayingCopyWithImpl; +@useResult +$Res call({ + int duration, int elapsed, Song song +}); + + +$SongCopyWith<$Res> get song; + +} +/// @nodoc +class _$NowPlayingCopyWithImpl<$Res> + implements $NowPlayingCopyWith<$Res> { + _$NowPlayingCopyWithImpl(this._self, this._then); + + final NowPlaying _self; + final $Res Function(NowPlaying) _then; + +/// Create a copy of NowPlaying +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? duration = null,Object? elapsed = null,Object? song = null,}) { + return _then(_self.copyWith( +duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable +as int,elapsed: null == elapsed ? _self.elapsed : elapsed // ignore: cast_nullable_to_non_nullable +as int,song: null == song ? _self.song : song // ignore: cast_nullable_to_non_nullable +as Song, + )); +} +/// Create a copy of NowPlaying +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SongCopyWith<$Res> get song { + + return $SongCopyWith<$Res>(_self.song, (value) { + return _then(_self.copyWith(song: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [NowPlaying]. +extension NowPlayingPatterns on NowPlaying { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _NowPlaying value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _NowPlaying() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _NowPlaying value) $default,){ +final _that = this; +switch (_that) { +case _NowPlaying(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _NowPlaying value)? $default,){ +final _that = this; +switch (_that) { +case _NowPlaying() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int duration, int elapsed, Song song)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _NowPlaying() when $default != null: +return $default(_that.duration,_that.elapsed,_that.song);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int duration, int elapsed, Song song) $default,) {final _that = this; +switch (_that) { +case _NowPlaying(): +return $default(_that.duration,_that.elapsed,_that.song);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int duration, int elapsed, Song song)? $default,) {final _that = this; +switch (_that) { +case _NowPlaying() when $default != null: +return $default(_that.duration,_that.elapsed,_that.song);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _NowPlaying implements NowPlaying { + const _NowPlaying({required this.duration, required this.elapsed, required this.song}); + factory _NowPlaying.fromJson(Map json) => _$NowPlayingFromJson(json); + +@override final int duration; +@override final int elapsed; +@override final Song song; + +/// Create a copy of NowPlaying +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$NowPlayingCopyWith<_NowPlaying> get copyWith => __$NowPlayingCopyWithImpl<_NowPlaying>(this, _$identity); + +@override +Map toJson() { + return _$NowPlayingToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _NowPlaying&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.elapsed, elapsed) || other.elapsed == elapsed)&&(identical(other.song, song) || other.song == song)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,duration,elapsed,song); + +@override +String toString() { + return 'NowPlaying(duration: $duration, elapsed: $elapsed, song: $song)'; +} + + +} + +/// @nodoc +abstract mixin class _$NowPlayingCopyWith<$Res> implements $NowPlayingCopyWith<$Res> { + factory _$NowPlayingCopyWith(_NowPlaying value, $Res Function(_NowPlaying) _then) = __$NowPlayingCopyWithImpl; +@override @useResult +$Res call({ + int duration, int elapsed, Song song +}); + + +@override $SongCopyWith<$Res> get song; + +} +/// @nodoc +class __$NowPlayingCopyWithImpl<$Res> + implements _$NowPlayingCopyWith<$Res> { + __$NowPlayingCopyWithImpl(this._self, this._then); + + final _NowPlaying _self; + final $Res Function(_NowPlaying) _then; + +/// Create a copy of NowPlaying +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? duration = null,Object? elapsed = null,Object? song = null,}) { + return _then(_NowPlaying( +duration: null == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable +as int,elapsed: null == elapsed ? _self.elapsed : elapsed // ignore: cast_nullable_to_non_nullable +as int,song: null == song ? _self.song : song // ignore: cast_nullable_to_non_nullable +as Song, + )); +} + +/// Create a copy of NowPlaying +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SongCopyWith<$Res> get song { + + return $SongCopyWith<$Res>(_self.song, (value) { + return _then(_self.copyWith(song: value)); + }); +} +} + + +/// @nodoc +mixin _$PlayingNext { + + Song get song; +/// Create a copy of PlayingNext +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PlayingNextCopyWith get copyWith => _$PlayingNextCopyWithImpl(this as PlayingNext, _$identity); + + /// Serializes this PlayingNext to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PlayingNext&&(identical(other.song, song) || other.song == song)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,song); + +@override +String toString() { + return 'PlayingNext(song: $song)'; +} + + +} + +/// @nodoc +abstract mixin class $PlayingNextCopyWith<$Res> { + factory $PlayingNextCopyWith(PlayingNext value, $Res Function(PlayingNext) _then) = _$PlayingNextCopyWithImpl; +@useResult +$Res call({ + Song song +}); + + +$SongCopyWith<$Res> get song; + +} +/// @nodoc +class _$PlayingNextCopyWithImpl<$Res> + implements $PlayingNextCopyWith<$Res> { + _$PlayingNextCopyWithImpl(this._self, this._then); + + final PlayingNext _self; + final $Res Function(PlayingNext) _then; + +/// Create a copy of PlayingNext +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? song = null,}) { + return _then(_self.copyWith( +song: null == song ? _self.song : song // ignore: cast_nullable_to_non_nullable +as Song, + )); +} +/// Create a copy of PlayingNext +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SongCopyWith<$Res> get song { + + return $SongCopyWith<$Res>(_self.song, (value) { + return _then(_self.copyWith(song: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [PlayingNext]. +extension PlayingNextPatterns on PlayingNext { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PlayingNext value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PlayingNext() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PlayingNext value) $default,){ +final _that = this; +switch (_that) { +case _PlayingNext(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PlayingNext value)? $default,){ +final _that = this; +switch (_that) { +case _PlayingNext() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( Song song)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PlayingNext() when $default != null: +return $default(_that.song);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( Song song) $default,) {final _that = this; +switch (_that) { +case _PlayingNext(): +return $default(_that.song);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( Song song)? $default,) {final _that = this; +switch (_that) { +case _PlayingNext() when $default != null: +return $default(_that.song);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _PlayingNext implements PlayingNext { + const _PlayingNext({required this.song}); + factory _PlayingNext.fromJson(Map json) => _$PlayingNextFromJson(json); + +@override final Song song; + +/// Create a copy of PlayingNext +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PlayingNextCopyWith<_PlayingNext> get copyWith => __$PlayingNextCopyWithImpl<_PlayingNext>(this, _$identity); + +@override +Map toJson() { + return _$PlayingNextToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PlayingNext&&(identical(other.song, song) || other.song == song)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,song); + +@override +String toString() { + return 'PlayingNext(song: $song)'; +} + + +} + +/// @nodoc +abstract mixin class _$PlayingNextCopyWith<$Res> implements $PlayingNextCopyWith<$Res> { + factory _$PlayingNextCopyWith(_PlayingNext value, $Res Function(_PlayingNext) _then) = __$PlayingNextCopyWithImpl; +@override @useResult +$Res call({ + Song song +}); + + +@override $SongCopyWith<$Res> get song; + +} +/// @nodoc +class __$PlayingNextCopyWithImpl<$Res> + implements _$PlayingNextCopyWith<$Res> { + __$PlayingNextCopyWithImpl(this._self, this._then); + + final _PlayingNext _self; + final $Res Function(_PlayingNext) _then; + +/// Create a copy of PlayingNext +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? song = null,}) { + return _then(_PlayingNext( +song: null == song ? _self.song : song // ignore: cast_nullable_to_non_nullable +as Song, + )); +} + +/// Create a copy of PlayingNext +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SongCopyWith<$Res> get song { + + return $SongCopyWith<$Res>(_self.song, (value) { + return _then(_self.copyWith(song: value)); + }); +} +} + + +/// @nodoc +mixin _$Song { + + String get id; String get title; String get artist; String get album; String get art; +/// Create a copy of Song +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SongCopyWith get copyWith => _$SongCopyWithImpl(this as Song, _$identity); + + /// Serializes this Song to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Song&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.artist, artist) || other.artist == artist)&&(identical(other.album, album) || other.album == album)&&(identical(other.art, art) || other.art == art)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,title,artist,album,art); + +@override +String toString() { + return 'Song(id: $id, title: $title, artist: $artist, album: $album, art: $art)'; +} + + +} + +/// @nodoc +abstract mixin class $SongCopyWith<$Res> { + factory $SongCopyWith(Song value, $Res Function(Song) _then) = _$SongCopyWithImpl; +@useResult +$Res call({ + String id, String title, String artist, String album, String art +}); + + + + +} +/// @nodoc +class _$SongCopyWithImpl<$Res> + implements $SongCopyWith<$Res> { + _$SongCopyWithImpl(this._self, this._then); + + final Song _self; + final $Res Function(Song) _then; + +/// Create a copy of Song +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? artist = null,Object? album = null,Object? art = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,artist: null == artist ? _self.artist : artist // ignore: cast_nullable_to_non_nullable +as String,album: null == album ? _self.album : album // ignore: cast_nullable_to_non_nullable +as String,art: null == art ? _self.art : art // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Song]. +extension SongPatterns on Song { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Song value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Song() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Song value) $default,){ +final _that = this; +switch (_that) { +case _Song(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Song value)? $default,){ +final _that = this; +switch (_that) { +case _Song() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String title, String artist, String album, String art)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Song() when $default != null: +return $default(_that.id,_that.title,_that.artist,_that.album,_that.art);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String title, String artist, String album, String art) $default,) {final _that = this; +switch (_that) { +case _Song(): +return $default(_that.id,_that.title,_that.artist,_that.album,_that.art);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String title, String artist, String album, String art)? $default,) {final _that = this; +switch (_that) { +case _Song() when $default != null: +return $default(_that.id,_that.title,_that.artist,_that.album,_that.art);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Song implements Song { + const _Song({required this.id, required this.title, required this.artist, required this.album, required this.art}); + factory _Song.fromJson(Map json) => _$SongFromJson(json); + +@override final String id; +@override final String title; +@override final String artist; +@override final String album; +@override final String art; + +/// Create a copy of Song +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SongCopyWith<_Song> get copyWith => __$SongCopyWithImpl<_Song>(this, _$identity); + +@override +Map toJson() { + return _$SongToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Song&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.artist, artist) || other.artist == artist)&&(identical(other.album, album) || other.album == album)&&(identical(other.art, art) || other.art == art)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,title,artist,album,art); + +@override +String toString() { + return 'Song(id: $id, title: $title, artist: $artist, album: $album, art: $art)'; +} + + +} + +/// @nodoc +abstract mixin class _$SongCopyWith<$Res> implements $SongCopyWith<$Res> { + factory _$SongCopyWith(_Song value, $Res Function(_Song) _then) = __$SongCopyWithImpl; +@override @useResult +$Res call({ + String id, String title, String artist, String album, String art +}); + + + + +} +/// @nodoc +class __$SongCopyWithImpl<$Res> + implements _$SongCopyWith<$Res> { + __$SongCopyWithImpl(this._self, this._then); + + final _Song _self; + final $Res Function(_Song) _then; + +/// Create a copy of Song +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? artist = null,Object? album = null,Object? art = null,}) { + return _then(_Song( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,artist: null == artist ? _self.artist : artist // ignore: cast_nullable_to_non_nullable +as String,album: null == album ? _self.album : album // ignore: cast_nullable_to_non_nullable +as String,art: null == art ? _self.art : art // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/code_radio/models/code_radio_model.g.dart b/mobile-app-new/lib/code_radio/models/code_radio_model.g.dart new file mode 100644 index 000000000..3eb690a6d --- /dev/null +++ b/mobile-app-new/lib/code_radio/models/code_radio_model.g.dart @@ -0,0 +1,72 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'code_radio_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_CodeRadio _$CodeRadioFromJson(Map json) => _CodeRadio( + station: Station.fromJson(json['station'] as Map), + listeners: Listeners.fromJson(json['listeners'] as Map), + nowPlaying: NowPlaying.fromJson(json['now_playing'] as Map), + playingNext: PlayingNext.fromJson( + json['playing_next'] as Map, + ), +); + +Map _$CodeRadioToJson(_CodeRadio instance) => + { + 'station': instance.station, + 'listeners': instance.listeners, + 'now_playing': instance.nowPlaying, + 'playing_next': instance.playingNext, + }; + +_Station _$StationFromJson(Map json) => + _Station(listenUrl: json['listen_url'] as String); + +Map _$StationToJson(_Station instance) => { + 'listen_url': instance.listenUrl, +}; + +_Listeners _$ListenersFromJson(Map json) => + _Listeners(total: (json['total'] as num).toInt()); + +Map _$ListenersToJson(_Listeners instance) => + {'total': instance.total}; + +_NowPlaying _$NowPlayingFromJson(Map json) => _NowPlaying( + duration: (json['duration'] as num).toInt(), + elapsed: (json['elapsed'] as num).toInt(), + song: Song.fromJson(json['song'] as Map), +); + +Map _$NowPlayingToJson(_NowPlaying instance) => + { + 'duration': instance.duration, + 'elapsed': instance.elapsed, + 'song': instance.song, + }; + +_PlayingNext _$PlayingNextFromJson(Map json) => + _PlayingNext(song: Song.fromJson(json['song'] as Map)); + +Map _$PlayingNextToJson(_PlayingNext instance) => + {'song': instance.song}; + +_Song _$SongFromJson(Map json) => _Song( + id: json['id'] as String, + title: json['title'] as String, + artist: json['artist'] as String, + album: json['album'] as String, + art: json['art'] as String, +); + +Map _$SongToJson(_Song instance) => { + 'id': instance.id, + 'title': instance.title, + 'artist': instance.artist, + 'album': instance.album, + 'art': instance.art, +}; diff --git a/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart b/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart new file mode 100644 index 000000000..e85330396 --- /dev/null +++ b/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart @@ -0,0 +1,44 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +part 'websocket_repository.g.dart'; + +const _url = + 'wss://coderadio-admin-v2.freecodecamp.org/api/live/nowplaying/websocket'; + +const _station = 'station:coderadio'; + +@riverpod +CodeRadioWebsocketRepository codeRadioWebsocketRepository(Ref ref) { + final repository = CodeRadioWebsocketRepository(); + ref.onDispose(repository.dispose); + return repository; +} + +class CodeRadioWebsocketRepository { + WebSocketChannel? _channel; + + Stream connect() async* { + await _channel?.sink.close(); + + final channel = _channel = WebSocketChannel.connect(Uri.parse(_url)); + + await channel.ready; + + channel.sink.add( + jsonEncode({ + 'subs': {_station: {}}, + }), + ); + + yield* channel.stream.cast(); + } + + void dispose() { + log('Disposing CodeRadioWebsocketRepository'); + _channel?.sink.close(); + } +} diff --git a/mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart b/mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart new file mode 100644 index 000000000..85da241f0 --- /dev/null +++ b/mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'websocket_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(codeRadioWebsocketRepository) +final codeRadioWebsocketRepositoryProvider = + CodeRadioWebsocketRepositoryProvider._(); + +final class CodeRadioWebsocketRepositoryProvider + extends + $FunctionalProvider< + CodeRadioWebsocketRepository, + CodeRadioWebsocketRepository, + CodeRadioWebsocketRepository + > + with $Provider { + CodeRadioWebsocketRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'codeRadioWebsocketRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$codeRadioWebsocketRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + CodeRadioWebsocketRepository create(Ref ref) { + return codeRadioWebsocketRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(CodeRadioWebsocketRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$codeRadioWebsocketRepositoryHash() => + r'6ba28b15535756ff4a682f6e08c0e301f3c47732'; diff --git a/mobile-app-new/lib/code_radio/services/code_radio_service.dart b/mobile-app-new/lib/code_radio/services/code_radio_service.dart new file mode 100644 index 000000000..c7d8a0753 --- /dev/null +++ b/mobile-app-new/lib/code_radio/services/code_radio_service.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +import 'package:mobile_app_new/code_radio/models/code_radio_model.dart'; +import 'package:mobile_app_new/code_radio/repositories/websocket_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'code_radio_service.g.dart'; + +@riverpod +CodeRadioService codeRadioService(Ref ref) { + final repo = ref.watch(codeRadioWebsocketRepositoryProvider); + return CodeRadioService(repo); +} + +class CodeRadioService { + final CodeRadioWebsocketRepository _repository; + + CodeRadioService(this._repository); + + Stream get nowPlaying => _repository + .connect() + .map((frame) => jsonDecode(frame) as Map) + .where((frame) => frame.containsKey('pub')) + .map( + (frame) => CodeRadio.fromJson( + frame['pub']['data']['np'] as Map, + ), + ); +} diff --git a/mobile-app-new/lib/code_radio/services/code_radio_service.g.dart b/mobile-app-new/lib/code_radio/services/code_radio_service.g.dart new file mode 100644 index 000000000..bd50bafc6 --- /dev/null +++ b/mobile-app-new/lib/code_radio/services/code_radio_service.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'code_radio_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(codeRadioService) +final codeRadioServiceProvider = CodeRadioServiceProvider._(); + +final class CodeRadioServiceProvider + extends + $FunctionalProvider< + CodeRadioService, + CodeRadioService, + CodeRadioService + > + with $Provider { + CodeRadioServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'codeRadioServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$codeRadioServiceHash(); + + @$internal + @override + $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement(pointer); + + @override + CodeRadioService create(Ref ref) { + return codeRadioService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(CodeRadioService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$codeRadioServiceHash() => r'9ce102b6a6ce4ebc4977914b53276ec06277646a'; diff --git a/mobile-app-new/lib/code_radio/ui/player/player_view.dart b/mobile-app-new/lib/code_radio/ui/player/player_view.dart new file mode 100644 index 000000000..e512c45c5 --- /dev/null +++ b/mobile-app-new/lib/code_radio/ui/player/player_view.dart @@ -0,0 +1,224 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/code_radio/models/code_radio_model.dart'; +import 'package:mobile_app_new/code_radio/ui/player/player_viewmodel.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +const _nextSongMinHeight = 600.0; + +class CodeRadioPlayerView extends ConsumerWidget { + const CodeRadioPlayerView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final nowPlaying = ref.watch(codeRadioNowPlayingProvider); + + return Scaffold( + backgroundColor: FccColors.gray90, + appBar: AppBar(title: const Text('Code Radio')), + drawer: const DrawerWidget(), + body: Padding( + padding: const EdgeInsets.all(16), + child: switch (nowPlaying) { + AsyncData(:final value) => _Player(radio: value), + AsyncError() => ErrorRetry( + message: 'Unable to load Code Radio.\nPlease try again.', + onRetry: () => ref.invalidate(codeRadioNowPlayingProvider), + ), + AsyncLoading() => const Center(child: CircularProgressIndicator()), + }, + ), + ); + } +} + +class _Player extends StatelessWidget { + const _Player({required this.radio}); + + final CodeRadio radio; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + CodeRadioAlbumArt(radio: radio), + Padding( + padding: const EdgeInsets.only(top: 16), + child: CodeRadioNowPlaying(radio: radio), + ), + if (MediaQuery.sizeOf(context).height > _nextSongMinHeight) + Padding( + padding: const EdgeInsets.only(top: 16), + child: CodeRadioNextSong(song: radio.playingNext.song), + ), + const Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Row(children: [Expanded(child: CodeRadioPlayPauseButton())]), + ], + ), + ), + ], + ); + } +} + +class CodeRadioAlbumArt extends StatelessWidget { + const CodeRadioAlbumArt({super.key, required this.radio}); + + final CodeRadio radio; + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.45, + ), + child: AspectRatio( + aspectRatio: 1, + child: ColoredBox( + color: FccColors.gray80, + child: CachedNetworkImage( + imageUrl: radio.nowPlaying.song.art, + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + '${radio.listeners.total} listening right now', + textAlign: TextAlign.center, + style: const TextStyle( + color: Color.fromRGBO(1, 1, 1, 0.5), + fontSize: 36, + fontWeight: FontWeight.w800, + ), + ), + ], + ); + } +} + +class CodeRadioNowPlaying extends StatelessWidget { + const CodeRadioNowPlaying({super.key, required this.radio}); + + final CodeRadio radio; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(8), + color: FccColors.gray80, + child: Column( + children: [ + Text( + radio.nowPlaying.song.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + height: 1.5, + ), + ), + Row( + children: [ + Expanded( + child: Text( + radio.nowPlaying.song.artist, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16, height: 1.5), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: CodeRadioProgressBar(duration: radio.nowPlaying.duration), + ), + ], + ), + ); + } +} + +class CodeRadioProgressBar extends ConsumerWidget { + const CodeRadioProgressBar({super.key, required this.duration}); + + final int duration; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isPlaying = ref.watch(codeRadioPlayerProvider); + + if (!isPlaying) { + return const Visibility( + visible: false, + maintainSize: true, + maintainAnimation: true, + maintainState: true, + child: LinearProgressIndicator(value: 0), + ); + } + + final elapsed = ref.watch(codeRadioElapsedProvider).value; + + return LinearProgressIndicator( + value: duration == 0 ? null : (elapsed ?? 0) / duration, + ); + } +} + +class CodeRadioNextSong extends StatelessWidget { + const CodeRadioNextSong({super.key, required this.song}); + + final Song song; + + @override + Widget build(BuildContext context) { + return ListTile( + title: const Text('Next Song'), + subtitle: Row( + children: [ + Expanded( + child: Text( + '${song.title}\n${song.album}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + tileColor: FccColors.gray80, + isThreeLine: true, + titleAlignment: ListTileTitleAlignment.center, + leading: SizedBox.square( + dimension: 56, + child: CachedNetworkImage(imageUrl: song.art, fit: BoxFit.cover), + ), + ); + } +} + +class CodeRadioPlayPauseButton extends ConsumerWidget { + const CodeRadioPlayPauseButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isPlaying = ref.watch(codeRadioPlayerProvider); + + return ElevatedButton.icon( + style: ElevatedButton.styleFrom(backgroundColor: FccColors.gray80), + onPressed: () => ref.read(codeRadioPlayerProvider.notifier).toggle(), + icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow), + label: Text(isPlaying ? 'Pause' : 'Play'), + ); + } +} diff --git a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart new file mode 100644 index 000000000..37a100d31 --- /dev/null +++ b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart @@ -0,0 +1,58 @@ +import 'package:mobile_app_new/code_radio/models/code_radio_model.dart'; +import 'package:mobile_app_new/code_radio/services/code_radio_service.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'player_viewmodel.g.dart'; + +@riverpod +Stream codeRadioNowPlaying(Ref ref) => + ref.watch(codeRadioServiceProvider).nowPlaying; + +// The station only pushes on an event, so the seconds in between are counted +// locally. Every push resyncs the count to the server's elapsed. +@riverpod +Stream codeRadioElapsed(Ref ref) async* { + final radio = await ref.watch(codeRadioNowPlayingProvider.future); + + yield radio.nowPlaying.elapsed; + + yield* Stream.periodic( + const Duration(seconds: 1), + (tick) => radio.nowPlaying.elapsed + tick + 1, + ).takeWhile((elapsed) => elapsed <= radio.nowPlaying.duration); +} + +@riverpod +class CodeRadioPlayerNotifier extends _$CodeRadioPlayerNotifier { + AudioPlayerHandler get _handler => ref.read(audioHandlerProvider); + + @override + bool build() { + final subscription = _handler.playbackState.listen((playbackState) { + if (ref.mounted) state = playbackState.playing; + }); + ref.onDispose(subscription.cancel); + + ref.listen(codeRadioNowPlayingProvider, (_, next) { + final radio = next.value; + if (radio != null) _load(radio); + }, fireImmediately: true); + + return _handler.isPlayingCodeRadio; + } + + Future _load(CodeRadio radio) async { + if (_handler.codeRadioSongId == radio.nowPlaying.song.id) return; + + if (_handler.codeRadioSongId != null) { + _handler.updateCodeRadioSong(radio.nowPlaying.song); + return; + } + + await _handler.loadCodeRadio(radio); + await _handler.play(); + } + + Future toggle() => state ? _handler.pause() : _handler.play(); +} diff --git a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart new file mode 100644 index 000000000..c15406e7a --- /dev/null +++ b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart @@ -0,0 +1,131 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(codeRadioNowPlaying) +final codeRadioNowPlayingProvider = CodeRadioNowPlayingProvider._(); + +final class CodeRadioNowPlayingProvider + extends + $FunctionalProvider, CodeRadio, Stream> + with $FutureModifier, $StreamProvider { + CodeRadioNowPlayingProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'codeRadioNowPlayingProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$codeRadioNowPlayingHash(); + + @$internal + @override + $StreamProviderElement $createElement($ProviderPointer pointer) => + $StreamProviderElement(pointer); + + @override + Stream create(Ref ref) { + return codeRadioNowPlaying(ref); + } +} + +String _$codeRadioNowPlayingHash() => + r'd0f9afcd3106179e55eae66b421dde5852073477'; + +@ProviderFor(codeRadioElapsed) +final codeRadioElapsedProvider = CodeRadioElapsedProvider._(); + +final class CodeRadioElapsedProvider + extends $FunctionalProvider, int, Stream> + with $FutureModifier, $StreamProvider { + CodeRadioElapsedProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'codeRadioElapsedProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$codeRadioElapsedHash(); + + @$internal + @override + $StreamProviderElement $createElement($ProviderPointer pointer) => + $StreamProviderElement(pointer); + + @override + Stream create(Ref ref) { + return codeRadioElapsed(ref); + } +} + +String _$codeRadioElapsedHash() => r'c6a4ff9ba016e2d41f91b2df0ad0505a4cac9251'; + +@ProviderFor(CodeRadioPlayerNotifier) +final codeRadioPlayerProvider = CodeRadioPlayerNotifierProvider._(); + +final class CodeRadioPlayerNotifierProvider + extends $NotifierProvider { + CodeRadioPlayerNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'codeRadioPlayerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$codeRadioPlayerNotifierHash(); + + @$internal + @override + CodeRadioPlayerNotifier create() => CodeRadioPlayerNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(bool value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$codeRadioPlayerNotifierHash() => + r'c03d7ff4bd808ab8292ee9aeaec853a230fee942'; + +abstract class _$CodeRadioPlayerNotifier extends $Notifier { + bool build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + bool, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index 596baf5de..d11c6b55a 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -3,13 +3,21 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/routing/router.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; import 'package:mobile_app_new/services/dio_service.dart'; Future main() async { await dotenv.load(); await DioService().init(); - runApp(ProviderScope(child: const MyApp())); + runApp( + ProviderScope( + overrides: [ + audioHandlerProvider.overrideWithValue(await initAudioService()), + ], + child: const MyApp(), + ), + ); } class MyApp extends StatelessWidget { diff --git a/mobile-app-new/lib/routing/code_radio.dart b/mobile-app-new/lib/routing/code_radio.dart new file mode 100644 index 000000000..27e5591c1 --- /dev/null +++ b/mobile-app-new/lib/routing/code_radio.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/code_radio/ui/player/player_view.dart'; + +part 'code_radio.g.dart'; + +const codeRadioPath = '/code-radio'; + +@TypedGoRoute(path: codeRadioPath) +class CodeRadioRoute extends GoRouteData with $CodeRadioRoute { + const CodeRadioRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const CodeRadioPlayerView(); +} diff --git a/mobile-app-new/lib/routing/code_radio.g.dart b/mobile-app-new/lib/routing/code_radio.g.dart new file mode 100644 index 000000000..2f800ff67 --- /dev/null +++ b/mobile-app-new/lib/routing/code_radio.g.dart @@ -0,0 +1,36 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'code_radio.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [$codeRadioRoute]; + +RouteBase get $codeRadioRoute => GoRouteData.$route( + path: '/code-radio', + hasOverriddenOnExit: false, + factory: $CodeRadioRoute._fromState, +); + +mixin $CodeRadioRoute on GoRouteData { + static CodeRadioRoute _fromState(GoRouterState state) => + const CodeRadioRoute(); + + @override + String get location => GoRouteData.$location('/code-radio'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/mobile-app-new/lib/routing/router.dart b/mobile-app-new/lib/routing/router.dart index ed6513da4..28dc9498a 100644 --- a/mobile-app-new/lib/routing/router.dart +++ b/mobile-app-new/lib/routing/router.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/routing/code_radio.dart' as code_radio_routes; import 'package:mobile_app_new/routing/learn.dart' as learn_routes; import 'package:mobile_app_new/routing/news.dart' as news_routes; @@ -9,7 +10,11 @@ final _initialLocation = const learn_routes.LearnLandingRoute().location; final router = GoRouter( debugLogDiagnostics: kDebugMode, initialLocation: _initialLocation, - routes: [...learn_routes.$appRoutes, ...news_routes.$appRoutes], + routes: [ + ...learn_routes.$appRoutes, + ...news_routes.$appRoutes, + ...code_radio_routes.$appRoutes, + ], errorBuilder: (context, state) => Scaffold( appBar: AppBar(title: const Text('Page Not Found')), body: Center( diff --git a/mobile-app-new/lib/services/audio_service.dart b/mobile-app-new/lib/services/audio_service.dart new file mode 100644 index 000000000..4f9a720a4 --- /dev/null +++ b/mobile-app-new/lib/services/audio_service.dart @@ -0,0 +1,144 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:audio_service/audio_service.dart'; +import 'package:just_audio/just_audio.dart'; +import 'package:mobile_app_new/code_radio/models/code_radio_model.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'audio_service.g.dart'; + +sealed class AudioTypeConfig { + const AudioTypeConfig(); +} + +class CodeRadioAudioConfig extends AudioTypeConfig { + const CodeRadioAudioConfig(); +} + +// NOTE: The handler is created before runApp and injected, so reading +// this without that override is a wiring mistake rather than a lazy init. +@Riverpod(keepAlive: true) +AudioPlayerHandler audioHandler(Ref ref) => throw UnimplementedError( + 'audioHandlerProvider must be overridden in main() with initAudioService()', +); + +Future initAudioService() => AudioService.init( + builder: () => AudioPlayerHandler(), + config: const AudioServiceConfig( + androidNotificationChannelId: 'org.freecodecamp.channel.audio', + androidNotificationChannelName: 'Audio playback', + androidNotificationOngoing: true, + androidStopForegroundOnPause: true, + androidNotificationIcon: 'drawable/notification_icon', + notificationColor: FccColors.gray90, + ), +); + +class AudioPlayerHandler extends BaseAudioHandler { + AudioPlayerHandler() { + _notifyAudioHandlerAboutPlaybackEvents(); + } + + final AudioPlayer _audioPlayer = AudioPlayer(); + + AudioTypeConfig? _audioConfig; + + AudioTypeConfig? get audioConfig => _audioConfig; + + // The song the notification is currently showing, or null when the player + // holds something other than code radio. + String? get codeRadioSongId => + _audioConfig is CodeRadioAudioConfig ? mediaItem.value?.id : null; + + bool get isPlayingCodeRadio => + _audioPlayer.playing && _audioConfig is CodeRadioAudioConfig; + + @override + Future play() => _audioPlayer.play(); + + @override + Future pause() => _audioPlayer.pause(); + + @override + Future stop() async { + await _audioPlayer.stop(); + _audioConfig = null; + return super.stop(); + } + + @override + Future seek(Duration position) => _audioPlayer.seek(position); + + @override + Future onTaskRemoved() async { + await _audioPlayer.stop(); + return super.onTaskRemoved(); + } + + Future loadCodeRadio(CodeRadio radio) async { + try { + final song = _toMediaItem(radio.nowPlaying.song); + + await _audioPlayer.setAudioSource( + AudioSource.uri(Uri.parse(radio.station.listenUrl), tag: song), + ); + + _audioConfig = const CodeRadioAudioConfig(); + _publish(song); + } catch (e) { + log('loadCodeRadio: Cannot play audio: $e'); + } + } + + void updateCodeRadioSong(Song song) => _publish(_toMediaItem(song)); + + MediaItem _toMediaItem(Song song) => MediaItem( + id: song.id, + title: song.title, + artist: song.artist, + album: song.album, + artUri: Uri.parse(song.art), + ); + + void _publish(MediaItem song) { + queue.add([song]); + mediaItem.add(song); + } + + void _notifyAudioHandlerAboutPlaybackEvents() { + _audioPlayer.playbackEventStream.listen( + (PlaybackEvent event) { + final playing = _audioPlayer.playing; + playbackState.add( + playbackState.value.copyWith( + controls: [ + if (playing) MediaControl.pause else MediaControl.play, + MediaControl.stop, + ], + systemActions: const {}, + androidCompactActionIndices: const [0, 1], + processingState: const { + ProcessingState.idle: AudioProcessingState.idle, + ProcessingState.loading: AudioProcessingState.loading, + ProcessingState.buffering: AudioProcessingState.buffering, + ProcessingState.ready: AudioProcessingState.ready, + ProcessingState.completed: AudioProcessingState.completed, + }[_audioPlayer.processingState]!, + repeatMode: AudioServiceRepeatMode.none, + shuffleMode: AudioServiceShuffleMode.none, + playing: playing, + updatePosition: _audioPlayer.position, + bufferedPosition: _audioPlayer.bufferedPosition, + speed: _audioPlayer.speed, + queueIndex: event.currentIndex, + ), + ); + }, + onError: (Object e, StackTrace st) { + log('Playback event error: $e\n$st'); + }, + ); + } +} diff --git a/mobile-app-new/lib/services/audio_service.g.dart b/mobile-app-new/lib/services/audio_service.g.dart new file mode 100644 index 000000000..1a49beccc --- /dev/null +++ b/mobile-app-new/lib/services/audio_service.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'audio_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(audioHandler) +final audioHandlerProvider = AudioHandlerProvider._(); + +final class AudioHandlerProvider + extends + $FunctionalProvider< + AudioPlayerHandler, + AudioPlayerHandler, + AudioPlayerHandler + > + with $Provider { + AudioHandlerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'audioHandlerProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$audioHandlerHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + AudioPlayerHandler create(Ref ref) { + return audioHandler(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AudioPlayerHandler value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$audioHandlerHash() => r'014f1951b3e255a5440d3a291bb0cf719c0a8e20'; diff --git a/mobile-app-new/lib/widgets/drawer/drawer.dart b/mobile-app-new/lib/widgets/drawer/drawer.dart index b96956610..10c391b7a 100644 --- a/mobile-app-new/lib/widgets/drawer/drawer.dart +++ b/mobile-app-new/lib/widgets/drawer/drawer.dart @@ -1,5 +1,8 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/routing/code_radio.dart'; import 'package:mobile_app_new/routing/learn.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/widgets/drawer/drawer_tile.dart'; @@ -23,6 +26,12 @@ class DrawerWidget extends StatelessWidget { icon: Icons.forum_outlined, route: () => const NewsFeedRoute().go(context), ), + if (!Platform.isIOS) + DrawerTile( + component: 'Code Radio', + icon: Icons.radio, + route: () => const CodeRadioRoute().go(context), + ), ], ), ); diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 0e222f433..563136a3e 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -113,6 +113,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.1" + audio_service: + dependency: "direct main" + description: + name: audio_service + sha256: "95f3267f3449eb5cf71c8fcf1d556f57af1e898e2dc5815fb168d1843653edb7" + url: "https://pub.dev" + source: hosted + version: "0.18.19" + audio_service_platform_interface: + dependency: transitive + description: + name: audio_service_platform_interface + sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777" + url: "https://pub.dev" + source: hosted + version: "0.1.3" + audio_service_web: + dependency: transitive + description: + name: audio_service_web + sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df + url: "https://pub.dev" + source: hosted + version: "0.1.4" + audio_session: + dependency: transitive + description: + name: audio_session + sha256: f9e7711a0e24ca8b40f5d7ac374c3c6e55016f3157912badd18199cdbbca5c4d + url: "https://pub.dev" + source: hosted + version: "0.2.4" boolean_selector: dependency: transitive description: @@ -695,6 +727,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -711,6 +751,30 @@ packages: url: "https://pub.dev" source: hosted version: "6.14.1" + just_audio: + dependency: "direct main" + description: + name: just_audio + sha256: e60aa97b233ddea025e13dfac59195207f528fa5e9e4a4a49a8c0766701e5fe6 + url: "https://pub.dev" + source: hosted + version: "0.10.6" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" + url: "https://pub.dev" + source: hosted + version: "0.4.16" leak_tracker: dependency: transitive description: @@ -1437,7 +1501,7 @@ packages: source: hosted version: "1.0.1" web_socket_channel: - dependency: transitive + dependency: "direct main" description: name: web_socket_channel sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 454de89ba..6351d7745 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -57,6 +57,9 @@ dependencies: html: 0.15.6 path: 1.9.1 path_provider: 2.1.6 + audio_service: 0.18.19 + just_audio: 0.10.6 + web_socket_channel: 3.0.3 dev_dependencies: flutter_driver: From 137191de407671b1d4b830f04cf4aa352d40d184 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 11 Sep 2026 20:10:17 +0530 Subject: [PATCH 29/52] fix: update code radio audio metadata when in bg --- .../repositories/websocket_repository.dart | 2 +- .../repositories/websocket_repository.g.dart | 4 +- .../services/code_radio_service.dart | 103 +++++++++++++++--- .../services/code_radio_service.g.dart | 4 +- .../ui/player/player_viewmodel.dart | 16 +-- .../ui/player/player_viewmodel.g.dart | 4 +- .../lib/services/audio_service.dart | 52 +++++++-- 7 files changed, 148 insertions(+), 37 deletions(-) diff --git a/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart b/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart index e85330396..7123148c6 100644 --- a/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart +++ b/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart @@ -11,7 +11,7 @@ const _url = const _station = 'station:coderadio'; -@riverpod +@Riverpod(keepAlive: true) CodeRadioWebsocketRepository codeRadioWebsocketRepository(Ref ref) { final repository = CodeRadioWebsocketRepository(); ref.onDispose(repository.dispose); diff --git a/mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart b/mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart index 85da241f0..0a5306746 100644 --- a/mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart +++ b/mobile-app-new/lib/code_radio/repositories/websocket_repository.g.dart @@ -27,7 +27,7 @@ final class CodeRadioWebsocketRepositoryProvider argument: null, retry: null, name: r'codeRadioWebsocketRepositoryProvider', - isAutoDispose: true, + isAutoDispose: false, dependencies: null, $allTransitiveDependencies: null, ); @@ -56,4 +56,4 @@ final class CodeRadioWebsocketRepositoryProvider } String _$codeRadioWebsocketRepositoryHash() => - r'6ba28b15535756ff4a682f6e08c0e301f3c47732'; + r'734876525c907597668041a2538a87774fe7b281'; diff --git a/mobile-app-new/lib/code_radio/services/code_radio_service.dart b/mobile-app-new/lib/code_radio/services/code_radio_service.dart index c7d8a0753..84ff057a6 100644 --- a/mobile-app-new/lib/code_radio/services/code_radio_service.dart +++ b/mobile-app-new/lib/code_radio/services/code_radio_service.dart @@ -1,29 +1,104 @@ +import 'dart:async'; import 'dart:convert'; +import 'dart:developer'; import 'package:mobile_app_new/code_radio/models/code_radio_model.dart'; import 'package:mobile_app_new/code_radio/repositories/websocket_repository.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'code_radio_service.g.dart'; -@riverpod +@Riverpod(keepAlive: true) CodeRadioService codeRadioService(Ref ref) { - final repo = ref.watch(codeRadioWebsocketRepositoryProvider); - return CodeRadioService(repo); + final service = CodeRadioService( + ref.watch(codeRadioWebsocketRepositoryProvider), + ref.watch(audioHandlerProvider), + ); + + ref.onDispose(service.dispose); + + return service; } class CodeRadioService { + CodeRadioService(this._repository, this._handler); + final CodeRadioWebsocketRepository _repository; + final AudioPlayerHandler _handler; + + final StreamController _updates = + StreamController.broadcast(); + + StreamSubscription? _station; + StreamSubscription? _playback; + CodeRadio? _latest; + + bool _isLoaded = false; + + Stream get updates async* { + final latest = _latest; + if (latest != null) yield latest; + + yield* _updates.stream; + } + + void start() { + _station ??= _repository + .connect() + .map((frame) => jsonDecode(frame) as Map) + .where((frame) => frame.containsKey('pub')) + .map( + (frame) => CodeRadio.fromJson( + frame['pub']['data']['np'] as Map, + ), + ) + .listen( + _onFrame, + onError: (Object error, StackTrace stackTrace) { + log('Error in CodeRadioService: $error'); + _updates.addError(error, stackTrace); + _dropStation(); + }, + onDone: _dropStation, + ); + + _playback ??= _handler.playbackState.listen((_) { + if (_handler.codeRadioSongId != null) { + _isLoaded = true; + } else if (_isLoaded) { + _stop(); + } + }); + } + + void _stop() { + _station?.cancel(); + _station = null; + _isLoaded = false; + } + + void dispose() { + log('Disposing CodeRadioService'); + _station?.cancel(); + _playback?.cancel(); + _updates.close(); + } + + void _onFrame(CodeRadio radio) { + _latest = radio; + _updates.add(radio); + + final song = radio.nowPlaying.song; + + if (_handler.codeRadioSongId != null && + _handler.codeRadioSongId != song.id) { + _handler.updateCodeRadioSong(song); + } + } - CodeRadioService(this._repository); - - Stream get nowPlaying => _repository - .connect() - .map((frame) => jsonDecode(frame) as Map) - .where((frame) => frame.containsKey('pub')) - .map( - (frame) => CodeRadio.fromJson( - frame['pub']['data']['np'] as Map, - ), - ); + void _dropStation() { + _station?.cancel(); + _station = null; + } } diff --git a/mobile-app-new/lib/code_radio/services/code_radio_service.g.dart b/mobile-app-new/lib/code_radio/services/code_radio_service.g.dart index bd50bafc6..0de7fc2c4 100644 --- a/mobile-app-new/lib/code_radio/services/code_radio_service.g.dart +++ b/mobile-app-new/lib/code_radio/services/code_radio_service.g.dart @@ -26,7 +26,7 @@ final class CodeRadioServiceProvider argument: null, retry: null, name: r'codeRadioServiceProvider', - isAutoDispose: true, + isAutoDispose: false, dependencies: null, $allTransitiveDependencies: null, ); @@ -53,4 +53,4 @@ final class CodeRadioServiceProvider } } -String _$codeRadioServiceHash() => r'9ce102b6a6ce4ebc4977914b53276ec06277646a'; +String _$codeRadioServiceHash() => r'1334b2003f1086cb57717c0dcb876381666afe3f'; diff --git a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart index 37a100d31..717e86076 100644 --- a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart +++ b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart @@ -6,8 +6,13 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'player_viewmodel.g.dart'; @riverpod -Stream codeRadioNowPlaying(Ref ref) => - ref.watch(codeRadioServiceProvider).nowPlaying; +Stream codeRadioNowPlaying(Ref ref) { + final service = ref.watch(codeRadioServiceProvider); + + service.start(); + + return service.updates; +} // The station only pushes on an event, so the seconds in between are counted // locally. Every push resyncs the count to the server's elapsed. @@ -43,12 +48,7 @@ class CodeRadioPlayerNotifier extends _$CodeRadioPlayerNotifier { } Future _load(CodeRadio radio) async { - if (_handler.codeRadioSongId == radio.nowPlaying.song.id) return; - - if (_handler.codeRadioSongId != null) { - _handler.updateCodeRadioSong(radio.nowPlaying.song); - return; - } + if (_handler.codeRadioSongId != null) return; await _handler.loadCodeRadio(radio); await _handler.play(); diff --git a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart index c15406e7a..885246b5c 100644 --- a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart +++ b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart @@ -42,7 +42,7 @@ final class CodeRadioNowPlayingProvider } String _$codeRadioNowPlayingHash() => - r'd0f9afcd3106179e55eae66b421dde5852073477'; + r'642ff517d7359e73249732ea7ccbedfa1c04e48f'; @ProviderFor(codeRadioElapsed) final codeRadioElapsedProvider = CodeRadioElapsedProvider._(); @@ -110,7 +110,7 @@ final class CodeRadioPlayerNotifierProvider } String _$codeRadioPlayerNotifierHash() => - r'c03d7ff4bd808ab8292ee9aeaec853a230fee942'; + r'763710fdaf1ee286a50a7d40ccad330245da1e2a'; abstract class _$CodeRadioPlayerNotifier extends $Notifier { bool build(); diff --git a/mobile-app-new/lib/services/audio_service.dart b/mobile-app-new/lib/services/audio_service.dart index 4f9a720a4..9093e196e 100644 --- a/mobile-app-new/lib/services/audio_service.dart +++ b/mobile-app-new/lib/services/audio_service.dart @@ -45,10 +45,16 @@ class AudioPlayerHandler extends BaseAudioHandler { AudioTypeConfig? _audioConfig; + Uri? _codeRadioUrl; + + // NOTE: Pausing a live stream does not pause the source: the buffer keeps filling + // while the playhead sits still, so resuming plays out the backlog instead + // of what the station is broadcasting now. Reconnecting on resume is the + // only way back to the live edge. + bool _resumeNeedsLiveEdge = false; + AudioTypeConfig? get audioConfig => _audioConfig; - // The song the notification is currently showing, or null when the player - // holds something other than code radio. String? get codeRadioSongId => _audioConfig is CodeRadioAudioConfig ? mediaItem.value?.id : null; @@ -56,15 +62,31 @@ class AudioPlayerHandler extends BaseAudioHandler { _audioPlayer.playing && _audioConfig is CodeRadioAudioConfig; @override - Future play() => _audioPlayer.play(); + Future play() async { + if (_resumeNeedsLiveEdge) { + _resumeNeedsLiveEdge = false; + + if (_audioConfig is CodeRadioAudioConfig) { + await _rejoinCodeRadioLiveEdge(); + } + } + + await _audioPlayer.play(); + } @override - Future pause() => _audioPlayer.pause(); + Future pause() async { + if (_audioConfig is CodeRadioAudioConfig) _resumeNeedsLiveEdge = true; + + await _audioPlayer.pause(); + } @override Future stop() async { await _audioPlayer.stop(); _audioConfig = null; + _codeRadioUrl = null; + _resumeNeedsLiveEdge = false; return super.stop(); } @@ -73,18 +95,19 @@ class AudioPlayerHandler extends BaseAudioHandler { @override Future onTaskRemoved() async { - await _audioPlayer.stop(); + await stop(); return super.onTaskRemoved(); } Future loadCodeRadio(CodeRadio radio) async { try { final song = _toMediaItem(radio.nowPlaying.song); + final url = Uri.parse(radio.station.listenUrl); - await _audioPlayer.setAudioSource( - AudioSource.uri(Uri.parse(radio.station.listenUrl), tag: song), - ); + await _audioPlayer.setAudioSource(AudioSource.uri(url, tag: song)); + _codeRadioUrl = url; + _resumeNeedsLiveEdge = false; _audioConfig = const CodeRadioAudioConfig(); _publish(song); } catch (e) { @@ -94,6 +117,19 @@ class AudioPlayerHandler extends BaseAudioHandler { void updateCodeRadioSong(Song song) => _publish(_toMediaItem(song)); + Future _rejoinCodeRadioLiveEdge() async { + final url = _codeRadioUrl; + final song = mediaItem.value; + + if (url == null || song == null) return; + + try { + await _audioPlayer.setAudioSource(AudioSource.uri(url, tag: song)); + } catch (e) { + log('rejoinCodeRadioLiveEdge: Cannot reconnect: $e'); + } + } + MediaItem _toMediaItem(Song song) => MediaItem( id: song.id, title: song.title, From 56efb9c5617a54d08e8ba028c59a1fc910f6fe04 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Thu, 17 Sep 2026 22:30:19 +0530 Subject: [PATCH 30/52] feat: podcasts section --- mobile-app-new/android/app/build.gradle.kts | 5 + .../android/app/src/main/AndroidManifest.xml | 11 + mobile-app-new/ios/Runner/AppDelegate.swift | 2 + mobile-app-new/lib/main.dart | 30 +- mobile-app-new/lib/podcasts/constants.dart | 14 + .../controllers/downloads_controller.dart | 93 +++ .../downloads_controller.freezed.dart | 272 ++++++++ .../controllers/downloads_controller.g.dart | 73 ++ .../controllers/library_controller.dart | 29 + .../controllers/library_controller.g.dart | 55 ++ .../controllers/player_controller.dart | 148 ++++ .../player_controller.freezed.dart | 286 ++++++++ .../controllers/player_controller.g.dart | 98 +++ .../lib/podcasts/models/episode_model.dart | 50 ++ .../models/episode_model.freezed.dart | 295 ++++++++ .../lib/podcasts/models/episode_model.g.dart | 29 + .../lib/podcasts/models/podcast_model.dart | 21 + .../models/podcast_model.freezed.dart | 298 +++++++++ .../lib/podcasts/models/podcast_model.g.dart | 29 + .../podcasts/repositories/api_repository.dart | 54 ++ .../repositories/api_repository.g.dart | 58 ++ .../repositories/download_repository.dart | 200 ++++++ .../repositories/download_repository.g.dart | 58 ++ .../repositories/progress_repository.dart | 38 ++ .../repositories/progress_repository.g.dart | 58 ++ .../repositories/storage_repository.dart | 282 ++++++++ .../repositories/storage_repository.g.dart | 58 ++ .../lib/podcasts/services/api_service.dart | 68 ++ .../lib/podcasts/services/api_service.g.dart | 57 ++ .../podcasts/services/library_service.dart | 142 ++++ .../podcasts/services/library_service.g.dart | 58 ++ .../podcasts/services/playback_service.dart | 196 ++++++ .../podcasts/services/playback_service.g.dart | 58 ++ .../podcasts/ui/downloads/downloads_view.dart | 70 ++ .../lib/podcasts/ui/episode/episode_view.dart | 279 ++++++++ .../ui/episode_list/episode_list_view.dart | 260 ++++++++ .../episode_list/episode_list_viewmodel.dart | 110 +++ .../episode_list_viewmodel.freezed.dart | 631 ++++++++++++++++++ .../episode_list_viewmodel.g.dart | 108 +++ .../ui/podcast_list/podcast_list_view.dart | 46 ++ .../podcast_list/podcast_list_viewmodel.dart | 9 + .../podcast_list_viewmodel.g.dart | 49 ++ .../lib/podcasts/ui/podcasts_shell.dart | 52 ++ .../podcasts/ui/widgets/download_button.dart | 104 +++ .../lib/podcasts/ui/widgets/episode_tile.dart | 172 +++++ .../ui/widgets/podcast_grid_tile.dart | 77 +++ mobile-app-new/lib/routing/podcasts.dart | 91 +++ mobile-app-new/lib/routing/podcasts.g.dart | 176 +++++ mobile-app-new/lib/routing/router.dart | 2 + .../lib/services/audio_service.dart | 156 ++++- .../lib/services/notification_service.dart | 85 +++ .../lib/services/notification_service.g.dart | 58 ++ mobile-app-new/lib/widgets/drawer/drawer.dart | 6 + mobile-app-new/pubspec.lock | 74 +- mobile-app-new/pubspec.yaml | 2 + 55 files changed, 5821 insertions(+), 19 deletions(-) create mode 100644 mobile-app-new/lib/podcasts/constants.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/downloads_controller.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/downloads_controller.freezed.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/downloads_controller.g.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/library_controller.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/library_controller.g.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/player_controller.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/player_controller.freezed.dart create mode 100644 mobile-app-new/lib/podcasts/controllers/player_controller.g.dart create mode 100644 mobile-app-new/lib/podcasts/models/episode_model.dart create mode 100644 mobile-app-new/lib/podcasts/models/episode_model.freezed.dart create mode 100644 mobile-app-new/lib/podcasts/models/episode_model.g.dart create mode 100644 mobile-app-new/lib/podcasts/models/podcast_model.dart create mode 100644 mobile-app-new/lib/podcasts/models/podcast_model.freezed.dart create mode 100644 mobile-app-new/lib/podcasts/models/podcast_model.g.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/api_repository.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/api_repository.g.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/download_repository.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/download_repository.g.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/progress_repository.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/progress_repository.g.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/storage_repository.dart create mode 100644 mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart create mode 100644 mobile-app-new/lib/podcasts/services/api_service.dart create mode 100644 mobile-app-new/lib/podcasts/services/api_service.g.dart create mode 100644 mobile-app-new/lib/podcasts/services/library_service.dart create mode 100644 mobile-app-new/lib/podcasts/services/library_service.g.dart create mode 100644 mobile-app-new/lib/podcasts/services/playback_service.dart create mode 100644 mobile-app-new/lib/podcasts/services/playback_service.g.dart create mode 100644 mobile-app-new/lib/podcasts/ui/downloads/downloads_view.dart create mode 100644 mobile-app-new/lib/podcasts/ui/episode/episode_view.dart create mode 100644 mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart create mode 100644 mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart create mode 100644 mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.freezed.dart create mode 100644 mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart create mode 100644 mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart create mode 100644 mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.dart create mode 100644 mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.g.dart create mode 100644 mobile-app-new/lib/podcasts/ui/podcasts_shell.dart create mode 100644 mobile-app-new/lib/podcasts/ui/widgets/download_button.dart create mode 100644 mobile-app-new/lib/podcasts/ui/widgets/episode_tile.dart create mode 100644 mobile-app-new/lib/podcasts/ui/widgets/podcast_grid_tile.dart create mode 100644 mobile-app-new/lib/routing/podcasts.dart create mode 100644 mobile-app-new/lib/routing/podcasts.g.dart create mode 100644 mobile-app-new/lib/services/notification_service.dart create mode 100644 mobile-app-new/lib/services/notification_service.g.dart diff --git a/mobile-app-new/android/app/build.gradle.kts b/mobile-app-new/android/app/build.gradle.kts index 5a51d394c..7fc8cdee3 100644 --- a/mobile-app-new/android/app/build.gradle.kts +++ b/mobile-app-new/android/app/build.gradle.kts @@ -10,6 +10,7 @@ android { ndkVersion = flutter.ndkVersion compileOptions { + isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } @@ -34,6 +35,10 @@ android { } } +dependencies { + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") +} + kotlin { compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 diff --git a/mobile-app-new/android/app/src/main/AndroidManifest.xml b/mobile-app-new/android/app/src/main/AndroidManifest.xml index 7780a74f4..f7ec8a7ed 100644 --- a/mobile-app-new/android/app/src/main/AndroidManifest.xml +++ b/mobile-app-new/android/app/src/main/AndroidManifest.xml @@ -45,6 +45,17 @@ + + + + + + + + + Bool { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate return super.application(application, didFinishLaunchingWithOptions: launchOptions) } diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index d11c6b55a..cb06c6520 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -1,36 +1,54 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/services/library_service.dart'; import 'package:mobile_app_new/routing/router.dart'; import 'package:mobile_app_new/services/audio_service.dart'; import 'package:mobile_app_new/services/dio_service.dart'; +import 'package:mobile_app_new/services/notification_service.dart'; Future main() async { await dotenv.load(); await DioService().init(); + final audioHandler = await initAudioService(); + runApp( ProviderScope( - overrides: [ - audioHandlerProvider.overrideWithValue(await initAudioService()), - ], + overrides: [audioHandlerProvider.overrideWithValue(audioHandler)], child: const MyApp(), ), ); } -class MyApp extends StatelessWidget { +class MyApp extends ConsumerStatefulWidget { const MyApp({super.key}); - // This widget is the root of your application. + @override + ConsumerState createState() => _MyAppState(); +} + +class _MyAppState extends ConsumerState { + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + unawaited(ref.read(notificationServiceProvider).requestPermission()); + // NOTE: Deletes audio and artwork that no store row points at + unawaited(ref.read(podcastLibraryServiceProvider).sweepOrphanedFiles()); + }); + } + @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: router, title: 'Flutter Demo', theme: FccTheme.themeDark, - // home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } diff --git a/mobile-app-new/lib/podcasts/constants.dart b/mobile-app-new/lib/podcasts/constants.dart new file mode 100644 index 000000000..f205a5c6a --- /dev/null +++ b/mobile-app-new/lib/podcasts/constants.dart @@ -0,0 +1,14 @@ +// NOTE: Feed URLs for freeCodeCamp's own podcasts, pinned above the rest of +// the list. +const fccPodcastUrls = [ + // English + 'https://freecodecamp.libsyn.com/rss', + // Spanish + 'https://anchor.fm/s/ff0092f4/podcast/rss', + // Chinese + 'https://anchor.fm/s/ff054de4/podcast/rss', + // Portuguese + 'https://anchor.fm/s/ff026c00/podcast/rss', +]; + +const podcastSpeedOptions = [0.75, 1.0, 1.25, 1.5, 2.0]; diff --git a/mobile-app-new/lib/podcasts/controllers/downloads_controller.dart b/mobile-app-new/lib/podcasts/controllers/downloads_controller.dart new file mode 100644 index 000000000..020e421ad --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/downloads_controller.dart @@ -0,0 +1,93 @@ +import 'dart:developer'; + +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/misc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/podcasts/controllers/library_controller.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/services/library_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'downloads_controller.freezed.dart'; +part 'downloads_controller.g.dart'; + +@freezed +sealed class DownloadStatus with _$DownloadStatus { + const factory DownloadStatus.running(double progress) = DownloadRunning; + const factory DownloadStatus.failed() = DownloadFailed; +} + +@riverpod +class PodcastDownloadsNotifier extends _$PodcastDownloadsNotifier { + final _cancelTokens = {}; + + // NOTE: Pinned only while transfers are in flight, so an idle notifier is disposed. + KeepAliveLink? _link; + + @override + Map build() => const {}; + + Future start(Podcast podcast, Episode episode) async { + if (_cancelTokens.containsKey(episode.id)) return; + + final token = CancelToken(); + _cancelTokens[episode.id] = token; + _link ??= ref.keepAlive(); + _set(episode.id, const DownloadStatus.running(0)); + + try { + await ref + .read(podcastLibraryServiceProvider) + .download( + podcast, + episode, + cancelToken: token, + onProgress: (progress) { + if (_cancelTokens.containsKey(episode.id)) { + _set(episode.id, DownloadStatus.running(progress)); + } + }, + ); + + _clear(episode.id); + + // The library owns "downloaded"; this notifier only reports progress. + await ref.read(podcastLibraryProvider.notifier).refresh(); + } on DioException catch (e) { + if (CancelToken.isCancel(e)) { + _clear(episode.id); + } else { + log('Podcast download failed for ${episode.id}: $e'); + _set(episode.id, const DownloadStatus.failed()); + } + } catch (e) { + log('Podcast download failed for ${episode.id}: $e'); + _set(episode.id, const DownloadStatus.failed()); + } finally { + _cancelTokens.remove(episode.id); + _releaseWhenIdle(); + } + } + + void _releaseWhenIdle() { + if (_cancelTokens.isNotEmpty) return; + + _link?.close(); + _link = null; + } + + void cancel(String episodeId) { + _cancelTokens.remove(episodeId)?.cancel('cancelled by user'); + _clear(episodeId); + _releaseWhenIdle(); + } + + void _set(String episodeId, DownloadStatus status) { + state = {...state, episodeId: status}; + } + + void _clear(String episodeId) { + state = {...state}..remove(episodeId); + } +} diff --git a/mobile-app-new/lib/podcasts/controllers/downloads_controller.freezed.dart b/mobile-app-new/lib/podcasts/controllers/downloads_controller.freezed.dart new file mode 100644 index 000000000..249bf17bc --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/downloads_controller.freezed.dart @@ -0,0 +1,272 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'downloads_controller.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$DownloadStatus { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadStatus); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'DownloadStatus()'; +} + + +} + +/// @nodoc +class $DownloadStatusCopyWith<$Res> { +$DownloadStatusCopyWith(DownloadStatus _, $Res Function(DownloadStatus) __); +} + + +/// Adds pattern-matching-related methods to [DownloadStatus]. +extension DownloadStatusPatterns on DownloadStatus { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( DownloadRunning value)? running,TResult Function( DownloadFailed value)? failed,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case DownloadRunning() when running != null: +return running(_that);case DownloadFailed() when failed != null: +return failed(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( DownloadRunning value) running,required TResult Function( DownloadFailed value) failed,}){ +final _that = this; +switch (_that) { +case DownloadRunning(): +return running(_that);case DownloadFailed(): +return failed(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( DownloadRunning value)? running,TResult? Function( DownloadFailed value)? failed,}){ +final _that = this; +switch (_that) { +case DownloadRunning() when running != null: +return running(_that);case DownloadFailed() when failed != null: +return failed(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( double progress)? running,TResult Function()? failed,required TResult orElse(),}) {final _that = this; +switch (_that) { +case DownloadRunning() when running != null: +return running(_that.progress);case DownloadFailed() when failed != null: +return failed();case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( double progress) running,required TResult Function() failed,}) {final _that = this; +switch (_that) { +case DownloadRunning(): +return running(_that.progress);case DownloadFailed(): +return failed();} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( double progress)? running,TResult? Function()? failed,}) {final _that = this; +switch (_that) { +case DownloadRunning() when running != null: +return running(_that.progress);case DownloadFailed() when failed != null: +return failed();case _: + return null; + +} +} + +} + +/// @nodoc + + +class DownloadRunning implements DownloadStatus { + const DownloadRunning(this.progress); + + + final double progress; + +/// Create a copy of DownloadStatus +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$DownloadRunningCopyWith get copyWith => _$DownloadRunningCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadRunning&&(identical(other.progress, progress) || other.progress == progress)); +} + + +@override +int get hashCode => Object.hash(runtimeType,progress); + +@override +String toString() { + return 'DownloadStatus.running(progress: $progress)'; +} + + +} + +/// @nodoc +abstract mixin class $DownloadRunningCopyWith<$Res> implements $DownloadStatusCopyWith<$Res> { + factory $DownloadRunningCopyWith(DownloadRunning value, $Res Function(DownloadRunning) _then) = _$DownloadRunningCopyWithImpl; +@useResult +$Res call({ + double progress +}); + + + + +} +/// @nodoc +class _$DownloadRunningCopyWithImpl<$Res> + implements $DownloadRunningCopyWith<$Res> { + _$DownloadRunningCopyWithImpl(this._self, this._then); + + final DownloadRunning _self; + final $Res Function(DownloadRunning) _then; + +/// Create a copy of DownloadStatus +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? progress = null,}) { + return _then(DownloadRunning( +null == progress ? _self.progress : progress // ignore: cast_nullable_to_non_nullable +as double, + )); +} + + +} + +/// @nodoc + + +class DownloadFailed implements DownloadStatus { + const DownloadFailed(); + + + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadFailed); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'DownloadStatus.failed()'; +} + + +} + + + + +// dart format on diff --git a/mobile-app-new/lib/podcasts/controllers/downloads_controller.g.dart b/mobile-app-new/lib/podcasts/controllers/downloads_controller.g.dart new file mode 100644 index 000000000..33ecf5325 --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/downloads_controller.g.dart @@ -0,0 +1,73 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'downloads_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(PodcastDownloadsNotifier) +final podcastDownloadsProvider = PodcastDownloadsNotifierProvider._(); + +final class PodcastDownloadsNotifierProvider + extends + $NotifierProvider< + PodcastDownloadsNotifier, + Map + > { + PodcastDownloadsNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastDownloadsProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastDownloadsNotifierHash(); + + @$internal + @override + PodcastDownloadsNotifier create() => PodcastDownloadsNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(Map value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>(value), + ); + } +} + +String _$podcastDownloadsNotifierHash() => + r'b36ce1140f4480667777d8ab57418ada7ab98b98'; + +abstract class _$PodcastDownloadsNotifier + extends $Notifier> { + Map build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = + this.ref + as $Ref, Map>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + Map, + Map + >, + Map, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/podcasts/controllers/library_controller.dart b/mobile-app-new/lib/podcasts/controllers/library_controller.dart new file mode 100644 index 000000000..f37b286b5 --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/library_controller.dart @@ -0,0 +1,29 @@ +import 'package:mobile_app_new/podcasts/services/library_service.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'library_controller.g.dart'; + +@riverpod +class PodcastLibraryNotifier extends _$PodcastLibraryNotifier { + @override + Future build() => + ref.watch(podcastLibraryServiceProvider).getLibrary(); + + Future refresh() async { + state = AsyncData( + await ref.read(podcastLibraryServiceProvider).getLibrary(), + ); + } + + Future remove(String podcastId, String episodeId) async { + final handler = ref.read(audioHandlerProvider); + if (handler.episodeId == episodeId) await handler.stop(); + + await ref + .read(podcastLibraryServiceProvider) + .removeEpisode(podcastId, episodeId); + + await refresh(); + } +} diff --git a/mobile-app-new/lib/podcasts/controllers/library_controller.g.dart b/mobile-app-new/lib/podcasts/controllers/library_controller.g.dart new file mode 100644 index 000000000..65a0a1097 --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/library_controller.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'library_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(PodcastLibraryNotifier) +final podcastLibraryProvider = PodcastLibraryNotifierProvider._(); + +final class PodcastLibraryNotifierProvider + extends $AsyncNotifierProvider { + PodcastLibraryNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastLibraryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastLibraryNotifierHash(); + + @$internal + @override + PodcastLibraryNotifier create() => PodcastLibraryNotifier(); +} + +String _$podcastLibraryNotifierHash() => + r'72bd2cd9299b782bf76958b6b03fd0ca6ca966d8'; + +abstract class _$PodcastLibraryNotifier extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref, PodcastLibrary>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, PodcastLibrary>, + AsyncValue, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/podcasts/controllers/player_controller.dart b/mobile-app-new/lib/podcasts/controllers/player_controller.dart new file mode 100644 index 000000000..fa585ae88 --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/player_controller.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +import 'package:audio_service/audio_service.dart'; +import 'package:flutter_riverpod/misc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/services/playback_service.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'player_controller.freezed.dart'; +part 'player_controller.g.dart'; + +@freezed +abstract class PodcastPlayerState with _$PodcastPlayerState { + const factory PodcastPlayerState({ + String? episodeId, + String? loadingEpisodeId, + @Default(false) bool isPlaying, + @Default(false) bool isBuffering, + Duration? duration, + @Default(1.0) double speed, + }) = _PodcastPlayerState; + + const PodcastPlayerState._(); + + bool isCurrent(String id) => episodeId == id; + + bool isPlayingEpisode(String id) => isCurrent(id) && isPlaying; + + bool isBusyWith(String id) => + loadingEpisodeId == id || (isCurrent(id) && isBuffering); +} + +// NOTE: Kept off PodcastPlayerState deliberately. This ticks up to 60x a +// second +@riverpod +Stream podcastPlaybackPosition(Ref ref) => + ref.watch(audioHandlerProvider).positionStream; + +@riverpod +class PodcastPlayerNotifier extends _$PodcastPlayerNotifier { + // NOTE: Pinned while an episode is loaded, released once the handler holds + // none at all. + KeepAliveLink? _link; + + @override + PodcastPlayerState build() { + final handler = ref.watch(audioHandlerProvider); + + ref.watch(podcastPlaybackServiceProvider); + + final subscription = handler.playbackState.listen((playback) { + if (!ref.mounted) return; + state = _fromPlayback( + handler, + playback, + loadingEpisodeId: state.loadingEpisodeId, + ); + _syncKeepAlive(); + }); + + ref.onDispose(() { + subscription.cancel(); + _link?.close(); + _link = null; + }); + + return _fromPlayback(handler, handler.playbackState.value); + } + + void _syncKeepAlive() { + if (state.episodeId != null || state.loadingEpisodeId != null) { + _link ??= ref.keepAlive(); + return; + } + + _link?.close(); + _link = null; + } + + PodcastPlayerState _fromPlayback( + AudioPlayerHandler handler, + PlaybackState playback, { + String? loadingEpisodeId, + }) { + final episodeId = handler.episodeId; + + return PodcastPlayerState( + loadingEpisodeId: loadingEpisodeId, + episodeId: episodeId, + isPlaying: episodeId != null && playback.playing, + isBuffering: + episodeId != null && + const { + AudioProcessingState.loading, + AudioProcessingState.buffering, + }.contains(playback.processingState), + duration: handler.duration, + speed: playback.speed, + ); + } + + Future toggle(Podcast podcast, Episode episode) async { + final handler = ref.read(audioHandlerProvider); + + if (handler.episodeId == episode.id) { + return state.isPlaying ? handler.pause() : handler.play(); + } + + state = state.copyWith(loadingEpisodeId: episode.id); + _syncKeepAlive(); + + try { + final uris = await ref + .read(podcastPlaybackServiceProvider) + .playbackUris(podcast, episode); + + final loaded = await handler.loadEpisode( + episode, + podcast, + source: uris.audio, + artUri: uris.artwork, + startAt: await ref + .read(podcastPlaybackServiceProvider) + .readPosition(episode.id), + ); + + if (loaded) await handler.play(); + } finally { + if (ref.mounted) { + state = state.copyWith(loadingEpisodeId: null); + _syncKeepAlive(); + } + } + } + + Future seek(Duration position) => + ref.read(audioHandlerProvider).seek(position); + + Future fastForward() => ref.read(audioHandlerProvider).fastForward(); + + Future rewind() => ref.read(audioHandlerProvider).rewind(); + + Future setSpeed(double speed) => + ref.read(audioHandlerProvider).setSpeed(speed); +} diff --git a/mobile-app-new/lib/podcasts/controllers/player_controller.freezed.dart b/mobile-app-new/lib/podcasts/controllers/player_controller.freezed.dart new file mode 100644 index 000000000..c34a10121 --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/player_controller.freezed.dart @@ -0,0 +1,286 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'player_controller.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$PodcastPlayerState { + + String? get episodeId; String? get loadingEpisodeId; bool get isPlaying; bool get isBuffering; Duration? get duration; double get speed; +/// Create a copy of PodcastPlayerState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PodcastPlayerStateCopyWith get copyWith => _$PodcastPlayerStateCopyWithImpl(this as PodcastPlayerState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PodcastPlayerState&&(identical(other.episodeId, episodeId) || other.episodeId == episodeId)&&(identical(other.loadingEpisodeId, loadingEpisodeId) || other.loadingEpisodeId == loadingEpisodeId)&&(identical(other.isPlaying, isPlaying) || other.isPlaying == isPlaying)&&(identical(other.isBuffering, isBuffering) || other.isBuffering == isBuffering)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.speed, speed) || other.speed == speed)); +} + + +@override +int get hashCode => Object.hash(runtimeType,episodeId,loadingEpisodeId,isPlaying,isBuffering,duration,speed); + +@override +String toString() { + return 'PodcastPlayerState(episodeId: $episodeId, loadingEpisodeId: $loadingEpisodeId, isPlaying: $isPlaying, isBuffering: $isBuffering, duration: $duration, speed: $speed)'; +} + + +} + +/// @nodoc +abstract mixin class $PodcastPlayerStateCopyWith<$Res> { + factory $PodcastPlayerStateCopyWith(PodcastPlayerState value, $Res Function(PodcastPlayerState) _then) = _$PodcastPlayerStateCopyWithImpl; +@useResult +$Res call({ + String? episodeId, String? loadingEpisodeId, bool isPlaying, bool isBuffering, Duration? duration, double speed +}); + + + + +} +/// @nodoc +class _$PodcastPlayerStateCopyWithImpl<$Res> + implements $PodcastPlayerStateCopyWith<$Res> { + _$PodcastPlayerStateCopyWithImpl(this._self, this._then); + + final PodcastPlayerState _self; + final $Res Function(PodcastPlayerState) _then; + +/// Create a copy of PodcastPlayerState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? episodeId = freezed,Object? loadingEpisodeId = freezed,Object? isPlaying = null,Object? isBuffering = null,Object? duration = freezed,Object? speed = null,}) { + return _then(_self.copyWith( +episodeId: freezed == episodeId ? _self.episodeId : episodeId // ignore: cast_nullable_to_non_nullable +as String?,loadingEpisodeId: freezed == loadingEpisodeId ? _self.loadingEpisodeId : loadingEpisodeId // ignore: cast_nullable_to_non_nullable +as String?,isPlaying: null == isPlaying ? _self.isPlaying : isPlaying // ignore: cast_nullable_to_non_nullable +as bool,isBuffering: null == isBuffering ? _self.isBuffering : isBuffering // ignore: cast_nullable_to_non_nullable +as bool,duration: freezed == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable +as Duration?,speed: null == speed ? _self.speed : speed // ignore: cast_nullable_to_non_nullable +as double, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PodcastPlayerState]. +extension PodcastPlayerStatePatterns on PodcastPlayerState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PodcastPlayerState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PodcastPlayerState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PodcastPlayerState value) $default,){ +final _that = this; +switch (_that) { +case _PodcastPlayerState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PodcastPlayerState value)? $default,){ +final _that = this; +switch (_that) { +case _PodcastPlayerState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? episodeId, String? loadingEpisodeId, bool isPlaying, bool isBuffering, Duration? duration, double speed)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PodcastPlayerState() when $default != null: +return $default(_that.episodeId,_that.loadingEpisodeId,_that.isPlaying,_that.isBuffering,_that.duration,_that.speed);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? episodeId, String? loadingEpisodeId, bool isPlaying, bool isBuffering, Duration? duration, double speed) $default,) {final _that = this; +switch (_that) { +case _PodcastPlayerState(): +return $default(_that.episodeId,_that.loadingEpisodeId,_that.isPlaying,_that.isBuffering,_that.duration,_that.speed);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? episodeId, String? loadingEpisodeId, bool isPlaying, bool isBuffering, Duration? duration, double speed)? $default,) {final _that = this; +switch (_that) { +case _PodcastPlayerState() when $default != null: +return $default(_that.episodeId,_that.loadingEpisodeId,_that.isPlaying,_that.isBuffering,_that.duration,_that.speed);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PodcastPlayerState extends PodcastPlayerState { + const _PodcastPlayerState({this.episodeId, this.loadingEpisodeId, this.isPlaying = false, this.isBuffering = false, this.duration, this.speed = 1.0}): super._(); + + +@override final String? episodeId; +@override final String? loadingEpisodeId; +@override@JsonKey() final bool isPlaying; +@override@JsonKey() final bool isBuffering; +@override final Duration? duration; +@override@JsonKey() final double speed; + +/// Create a copy of PodcastPlayerState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PodcastPlayerStateCopyWith<_PodcastPlayerState> get copyWith => __$PodcastPlayerStateCopyWithImpl<_PodcastPlayerState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PodcastPlayerState&&(identical(other.episodeId, episodeId) || other.episodeId == episodeId)&&(identical(other.loadingEpisodeId, loadingEpisodeId) || other.loadingEpisodeId == loadingEpisodeId)&&(identical(other.isPlaying, isPlaying) || other.isPlaying == isPlaying)&&(identical(other.isBuffering, isBuffering) || other.isBuffering == isBuffering)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.speed, speed) || other.speed == speed)); +} + + +@override +int get hashCode => Object.hash(runtimeType,episodeId,loadingEpisodeId,isPlaying,isBuffering,duration,speed); + +@override +String toString() { + return 'PodcastPlayerState(episodeId: $episodeId, loadingEpisodeId: $loadingEpisodeId, isPlaying: $isPlaying, isBuffering: $isBuffering, duration: $duration, speed: $speed)'; +} + + +} + +/// @nodoc +abstract mixin class _$PodcastPlayerStateCopyWith<$Res> implements $PodcastPlayerStateCopyWith<$Res> { + factory _$PodcastPlayerStateCopyWith(_PodcastPlayerState value, $Res Function(_PodcastPlayerState) _then) = __$PodcastPlayerStateCopyWithImpl; +@override @useResult +$Res call({ + String? episodeId, String? loadingEpisodeId, bool isPlaying, bool isBuffering, Duration? duration, double speed +}); + + + + +} +/// @nodoc +class __$PodcastPlayerStateCopyWithImpl<$Res> + implements _$PodcastPlayerStateCopyWith<$Res> { + __$PodcastPlayerStateCopyWithImpl(this._self, this._then); + + final _PodcastPlayerState _self; + final $Res Function(_PodcastPlayerState) _then; + +/// Create a copy of PodcastPlayerState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? episodeId = freezed,Object? loadingEpisodeId = freezed,Object? isPlaying = null,Object? isBuffering = null,Object? duration = freezed,Object? speed = null,}) { + return _then(_PodcastPlayerState( +episodeId: freezed == episodeId ? _self.episodeId : episodeId // ignore: cast_nullable_to_non_nullable +as String?,loadingEpisodeId: freezed == loadingEpisodeId ? _self.loadingEpisodeId : loadingEpisodeId // ignore: cast_nullable_to_non_nullable +as String?,isPlaying: null == isPlaying ? _self.isPlaying : isPlaying // ignore: cast_nullable_to_non_nullable +as bool,isBuffering: null == isBuffering ? _self.isBuffering : isBuffering // ignore: cast_nullable_to_non_nullable +as bool,duration: freezed == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable +as Duration?,speed: null == speed ? _self.speed : speed // ignore: cast_nullable_to_non_nullable +as double, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/podcasts/controllers/player_controller.g.dart b/mobile-app-new/lib/podcasts/controllers/player_controller.g.dart new file mode 100644 index 000000000..5ed18fa27 --- /dev/null +++ b/mobile-app-new/lib/podcasts/controllers/player_controller.g.dart @@ -0,0 +1,98 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastPlaybackPosition) +final podcastPlaybackPositionProvider = PodcastPlaybackPositionProvider._(); + +final class PodcastPlaybackPositionProvider + extends + $FunctionalProvider, Duration, Stream> + with $FutureModifier, $StreamProvider { + PodcastPlaybackPositionProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastPlaybackPositionProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastPlaybackPositionHash(); + + @$internal + @override + $StreamProviderElement $createElement($ProviderPointer pointer) => + $StreamProviderElement(pointer); + + @override + Stream create(Ref ref) { + return podcastPlaybackPosition(ref); + } +} + +String _$podcastPlaybackPositionHash() => + r'16dbcaa698addbf6da17ae7a41caf71524e396c1'; + +@ProviderFor(PodcastPlayerNotifier) +final podcastPlayerProvider = PodcastPlayerNotifierProvider._(); + +final class PodcastPlayerNotifierProvider + extends $NotifierProvider { + PodcastPlayerNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastPlayerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastPlayerNotifierHash(); + + @$internal + @override + PodcastPlayerNotifier create() => PodcastPlayerNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastPlayerState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastPlayerNotifierHash() => + r'6fcf9fed68135e6e8a3a8dbb7c472f2759f826e6'; + +abstract class _$PodcastPlayerNotifier extends $Notifier { + PodcastPlayerState build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + PodcastPlayerState, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/podcasts/models/episode_model.dart b/mobile-app-new/lib/podcasts/models/episode_model.dart new file mode 100644 index 000000000..69a47c63f --- /dev/null +++ b/mobile-app-new/lib/podcasts/models/episode_model.dart @@ -0,0 +1,50 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'episode_model.freezed.dart'; +part 'episode_model.g.dart'; + +@freezed +abstract class Episode with _$Episode { + const factory Episode({ + @JsonKey(name: '_id') required String id, + required String podcastId, + required String title, + @Default('') String description, + DateTime? publicationDate, + required String audioUrl, + @DurationConverter() Duration? duration, + }) = _Episode; + + factory Episode.fromJson(Map json) => + _$EpisodeFromJson(json); +} + +// The API sends duration in whichever shape the source feed used: bare seconds +// ('3390'), 'MM:SS' or 'HH:MM:SS'. On disk it is Duration.toString() +// ('0:56:30.000000'). +class DurationConverter implements JsonConverter { + const DurationConverter(); + + @override + Duration? fromJson(String? json) => + json == null || json.isEmpty || json == 'null' + ? null + : _parseDuration(json); + + @override + String? toJson(Duration? object) => object?.toString(); +} + +Duration _parseDuration(String value) { + final parts = value.split(':'); + final secondsParts = parts.last.split('.'); + + return Duration( + hours: parts.length > 2 ? int.tryParse(parts[parts.length - 3]) ?? 0 : 0, + minutes: parts.length > 1 ? int.tryParse(parts[parts.length - 2]) ?? 0 : 0, + seconds: int.tryParse(secondsParts.first) ?? 0, + microseconds: secondsParts.length > 1 + ? int.tryParse(secondsParts[1]) ?? 0 + : 0, + ); +} diff --git a/mobile-app-new/lib/podcasts/models/episode_model.freezed.dart b/mobile-app-new/lib/podcasts/models/episode_model.freezed.dart new file mode 100644 index 000000000..c07077c6d --- /dev/null +++ b/mobile-app-new/lib/podcasts/models/episode_model.freezed.dart @@ -0,0 +1,295 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'episode_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Episode { + +@JsonKey(name: '_id') String get id; String get podcastId; String get title; String get description; DateTime? get publicationDate; String get audioUrl;@DurationConverter() Duration? get duration; +/// Create a copy of Episode +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$EpisodeCopyWith get copyWith => _$EpisodeCopyWithImpl(this as Episode, _$identity); + + /// Serializes this Episode to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Episode&&(identical(other.id, id) || other.id == id)&&(identical(other.podcastId, podcastId) || other.podcastId == podcastId)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.audioUrl, audioUrl) || other.audioUrl == audioUrl)&&(identical(other.duration, duration) || other.duration == duration)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,podcastId,title,description,publicationDate,audioUrl,duration); + +@override +String toString() { + return 'Episode(id: $id, podcastId: $podcastId, title: $title, description: $description, publicationDate: $publicationDate, audioUrl: $audioUrl, duration: $duration)'; +} + + +} + +/// @nodoc +abstract mixin class $EpisodeCopyWith<$Res> { + factory $EpisodeCopyWith(Episode value, $Res Function(Episode) _then) = _$EpisodeCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: '_id') String id, String podcastId, String title, String description, DateTime? publicationDate, String audioUrl,@DurationConverter() Duration? duration +}); + + + + +} +/// @nodoc +class _$EpisodeCopyWithImpl<$Res> + implements $EpisodeCopyWith<$Res> { + _$EpisodeCopyWithImpl(this._self, this._then); + + final Episode _self; + final $Res Function(Episode) _then; + +/// Create a copy of Episode +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? podcastId = null,Object? title = null,Object? description = null,Object? publicationDate = freezed,Object? audioUrl = null,Object? duration = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,podcastId: null == podcastId ? _self.podcastId : podcastId // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable +as DateTime?,audioUrl: null == audioUrl ? _self.audioUrl : audioUrl // ignore: cast_nullable_to_non_nullable +as String,duration: freezed == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable +as Duration?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Episode]. +extension EpisodePatterns on Episode { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Episode value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Episode() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Episode value) $default,){ +final _that = this; +switch (_that) { +case _Episode(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Episode value)? $default,){ +final _that = this; +switch (_that) { +case _Episode() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: '_id') String id, String podcastId, String title, String description, DateTime? publicationDate, String audioUrl, @DurationConverter() Duration? duration)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Episode() when $default != null: +return $default(_that.id,_that.podcastId,_that.title,_that.description,_that.publicationDate,_that.audioUrl,_that.duration);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: '_id') String id, String podcastId, String title, String description, DateTime? publicationDate, String audioUrl, @DurationConverter() Duration? duration) $default,) {final _that = this; +switch (_that) { +case _Episode(): +return $default(_that.id,_that.podcastId,_that.title,_that.description,_that.publicationDate,_that.audioUrl,_that.duration);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: '_id') String id, String podcastId, String title, String description, DateTime? publicationDate, String audioUrl, @DurationConverter() Duration? duration)? $default,) {final _that = this; +switch (_that) { +case _Episode() when $default != null: +return $default(_that.id,_that.podcastId,_that.title,_that.description,_that.publicationDate,_that.audioUrl,_that.duration);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Episode implements Episode { + const _Episode({@JsonKey(name: '_id') required this.id, required this.podcastId, required this.title, this.description = '', this.publicationDate, required this.audioUrl, @DurationConverter() this.duration}); + factory _Episode.fromJson(Map json) => _$EpisodeFromJson(json); + +@override@JsonKey(name: '_id') final String id; +@override final String podcastId; +@override final String title; +@override@JsonKey() final String description; +@override final DateTime? publicationDate; +@override final String audioUrl; +@override@DurationConverter() final Duration? duration; + +/// Create a copy of Episode +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$EpisodeCopyWith<_Episode> get copyWith => __$EpisodeCopyWithImpl<_Episode>(this, _$identity); + +@override +Map toJson() { + return _$EpisodeToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Episode&&(identical(other.id, id) || other.id == id)&&(identical(other.podcastId, podcastId) || other.podcastId == podcastId)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.audioUrl, audioUrl) || other.audioUrl == audioUrl)&&(identical(other.duration, duration) || other.duration == duration)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,podcastId,title,description,publicationDate,audioUrl,duration); + +@override +String toString() { + return 'Episode(id: $id, podcastId: $podcastId, title: $title, description: $description, publicationDate: $publicationDate, audioUrl: $audioUrl, duration: $duration)'; +} + + +} + +/// @nodoc +abstract mixin class _$EpisodeCopyWith<$Res> implements $EpisodeCopyWith<$Res> { + factory _$EpisodeCopyWith(_Episode value, $Res Function(_Episode) _then) = __$EpisodeCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: '_id') String id, String podcastId, String title, String description, DateTime? publicationDate, String audioUrl,@DurationConverter() Duration? duration +}); + + + + +} +/// @nodoc +class __$EpisodeCopyWithImpl<$Res> + implements _$EpisodeCopyWith<$Res> { + __$EpisodeCopyWithImpl(this._self, this._then); + + final _Episode _self; + final $Res Function(_Episode) _then; + +/// Create a copy of Episode +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? podcastId = null,Object? title = null,Object? description = null,Object? publicationDate = freezed,Object? audioUrl = null,Object? duration = freezed,}) { + return _then(_Episode( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,podcastId: null == podcastId ? _self.podcastId : podcastId // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable +as DateTime?,audioUrl: null == audioUrl ? _self.audioUrl : audioUrl // ignore: cast_nullable_to_non_nullable +as String,duration: freezed == duration ? _self.duration : duration // ignore: cast_nullable_to_non_nullable +as Duration?, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/podcasts/models/episode_model.g.dart b/mobile-app-new/lib/podcasts/models/episode_model.g.dart new file mode 100644 index 000000000..37be71d0b --- /dev/null +++ b/mobile-app-new/lib/podcasts/models/episode_model.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'episode_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Episode _$EpisodeFromJson(Map json) => _Episode( + id: json['_id'] as String, + podcastId: json['podcastId'] as String, + title: json['title'] as String, + description: json['description'] as String? ?? '', + publicationDate: json['publicationDate'] == null + ? null + : DateTime.parse(json['publicationDate'] as String), + audioUrl: json['audioUrl'] as String, + duration: const DurationConverter().fromJson(json['duration'] as String?), +); + +Map _$EpisodeToJson(_Episode instance) => { + '_id': instance.id, + 'podcastId': instance.podcastId, + 'title': instance.title, + 'description': instance.description, + 'publicationDate': instance.publicationDate?.toIso8601String(), + 'audioUrl': instance.audioUrl, + 'duration': const DurationConverter().toJson(instance.duration), +}; diff --git a/mobile-app-new/lib/podcasts/models/podcast_model.dart b/mobile-app-new/lib/podcasts/models/podcast_model.dart new file mode 100644 index 000000000..2c4d9a47d --- /dev/null +++ b/mobile-app-new/lib/podcasts/models/podcast_model.dart @@ -0,0 +1,21 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'podcast_model.freezed.dart'; +part 'podcast_model.g.dart'; + +@freezed +abstract class Podcast with _$Podcast { + const factory Podcast({ + @JsonKey(name: '_id') required String id, + required String feedUrl, + required String podcastLink, + required String title, + @Default('') String description, + required String imageLink, + @Default('') String copyright, + @Default(0) int numOfEps, + }) = _Podcast; + + factory Podcast.fromJson(Map json) => + _$PodcastFromJson(json); +} diff --git a/mobile-app-new/lib/podcasts/models/podcast_model.freezed.dart b/mobile-app-new/lib/podcasts/models/podcast_model.freezed.dart new file mode 100644 index 000000000..f4cdb2375 --- /dev/null +++ b/mobile-app-new/lib/podcasts/models/podcast_model.freezed.dart @@ -0,0 +1,298 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'podcast_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Podcast { + +@JsonKey(name: '_id') String get id; String get feedUrl; String get podcastLink; String get title; String get description; String get imageLink; String get copyright; int get numOfEps; +/// Create a copy of Podcast +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PodcastCopyWith get copyWith => _$PodcastCopyWithImpl(this as Podcast, _$identity); + + /// Serializes this Podcast to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Podcast&&(identical(other.id, id) || other.id == id)&&(identical(other.feedUrl, feedUrl) || other.feedUrl == feedUrl)&&(identical(other.podcastLink, podcastLink) || other.podcastLink == podcastLink)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageLink, imageLink) || other.imageLink == imageLink)&&(identical(other.copyright, copyright) || other.copyright == copyright)&&(identical(other.numOfEps, numOfEps) || other.numOfEps == numOfEps)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,feedUrl,podcastLink,title,description,imageLink,copyright,numOfEps); + +@override +String toString() { + return 'Podcast(id: $id, feedUrl: $feedUrl, podcastLink: $podcastLink, title: $title, description: $description, imageLink: $imageLink, copyright: $copyright, numOfEps: $numOfEps)'; +} + + +} + +/// @nodoc +abstract mixin class $PodcastCopyWith<$Res> { + factory $PodcastCopyWith(Podcast value, $Res Function(Podcast) _then) = _$PodcastCopyWithImpl; +@useResult +$Res call({ +@JsonKey(name: '_id') String id, String feedUrl, String podcastLink, String title, String description, String imageLink, String copyright, int numOfEps +}); + + + + +} +/// @nodoc +class _$PodcastCopyWithImpl<$Res> + implements $PodcastCopyWith<$Res> { + _$PodcastCopyWithImpl(this._self, this._then); + + final Podcast _self; + final $Res Function(Podcast) _then; + +/// Create a copy of Podcast +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? feedUrl = null,Object? podcastLink = null,Object? title = null,Object? description = null,Object? imageLink = null,Object? copyright = null,Object? numOfEps = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,feedUrl: null == feedUrl ? _self.feedUrl : feedUrl // ignore: cast_nullable_to_non_nullable +as String,podcastLink: null == podcastLink ? _self.podcastLink : podcastLink // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,imageLink: null == imageLink ? _self.imageLink : imageLink // ignore: cast_nullable_to_non_nullable +as String,copyright: null == copyright ? _self.copyright : copyright // ignore: cast_nullable_to_non_nullable +as String,numOfEps: null == numOfEps ? _self.numOfEps : numOfEps // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Podcast]. +extension PodcastPatterns on Podcast { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Podcast value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Podcast() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Podcast value) $default,){ +final _that = this; +switch (_that) { +case _Podcast(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Podcast value)? $default,){ +final _that = this; +switch (_that) { +case _Podcast() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: '_id') String id, String feedUrl, String podcastLink, String title, String description, String imageLink, String copyright, int numOfEps)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Podcast() when $default != null: +return $default(_that.id,_that.feedUrl,_that.podcastLink,_that.title,_that.description,_that.imageLink,_that.copyright,_that.numOfEps);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: '_id') String id, String feedUrl, String podcastLink, String title, String description, String imageLink, String copyright, int numOfEps) $default,) {final _that = this; +switch (_that) { +case _Podcast(): +return $default(_that.id,_that.feedUrl,_that.podcastLink,_that.title,_that.description,_that.imageLink,_that.copyright,_that.numOfEps);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: '_id') String id, String feedUrl, String podcastLink, String title, String description, String imageLink, String copyright, int numOfEps)? $default,) {final _that = this; +switch (_that) { +case _Podcast() when $default != null: +return $default(_that.id,_that.feedUrl,_that.podcastLink,_that.title,_that.description,_that.imageLink,_that.copyright,_that.numOfEps);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Podcast implements Podcast { + const _Podcast({@JsonKey(name: '_id') required this.id, required this.feedUrl, required this.podcastLink, required this.title, this.description = '', required this.imageLink, this.copyright = '', this.numOfEps = 0}); + factory _Podcast.fromJson(Map json) => _$PodcastFromJson(json); + +@override@JsonKey(name: '_id') final String id; +@override final String feedUrl; +@override final String podcastLink; +@override final String title; +@override@JsonKey() final String description; +@override final String imageLink; +@override@JsonKey() final String copyright; +@override@JsonKey() final int numOfEps; + +/// Create a copy of Podcast +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PodcastCopyWith<_Podcast> get copyWith => __$PodcastCopyWithImpl<_Podcast>(this, _$identity); + +@override +Map toJson() { + return _$PodcastToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Podcast&&(identical(other.id, id) || other.id == id)&&(identical(other.feedUrl, feedUrl) || other.feedUrl == feedUrl)&&(identical(other.podcastLink, podcastLink) || other.podcastLink == podcastLink)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.imageLink, imageLink) || other.imageLink == imageLink)&&(identical(other.copyright, copyright) || other.copyright == copyright)&&(identical(other.numOfEps, numOfEps) || other.numOfEps == numOfEps)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,feedUrl,podcastLink,title,description,imageLink,copyright,numOfEps); + +@override +String toString() { + return 'Podcast(id: $id, feedUrl: $feedUrl, podcastLink: $podcastLink, title: $title, description: $description, imageLink: $imageLink, copyright: $copyright, numOfEps: $numOfEps)'; +} + + +} + +/// @nodoc +abstract mixin class _$PodcastCopyWith<$Res> implements $PodcastCopyWith<$Res> { + factory _$PodcastCopyWith(_Podcast value, $Res Function(_Podcast) _then) = __$PodcastCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(name: '_id') String id, String feedUrl, String podcastLink, String title, String description, String imageLink, String copyright, int numOfEps +}); + + + + +} +/// @nodoc +class __$PodcastCopyWithImpl<$Res> + implements _$PodcastCopyWith<$Res> { + __$PodcastCopyWithImpl(this._self, this._then); + + final _Podcast _self; + final $Res Function(_Podcast) _then; + +/// Create a copy of Podcast +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? feedUrl = null,Object? podcastLink = null,Object? title = null,Object? description = null,Object? imageLink = null,Object? copyright = null,Object? numOfEps = null,}) { + return _then(_Podcast( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,feedUrl: null == feedUrl ? _self.feedUrl : feedUrl // ignore: cast_nullable_to_non_nullable +as String,podcastLink: null == podcastLink ? _self.podcastLink : podcastLink // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,imageLink: null == imageLink ? _self.imageLink : imageLink // ignore: cast_nullable_to_non_nullable +as String,copyright: null == copyright ? _self.copyright : copyright // ignore: cast_nullable_to_non_nullable +as String,numOfEps: null == numOfEps ? _self.numOfEps : numOfEps // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/podcasts/models/podcast_model.g.dart b/mobile-app-new/lib/podcasts/models/podcast_model.g.dart new file mode 100644 index 000000000..8cf1068d8 --- /dev/null +++ b/mobile-app-new/lib/podcasts/models/podcast_model.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'podcast_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Podcast _$PodcastFromJson(Map json) => _Podcast( + id: json['_id'] as String, + feedUrl: json['feedUrl'] as String, + podcastLink: json['podcastLink'] as String, + title: json['title'] as String, + description: json['description'] as String? ?? '', + imageLink: json['imageLink'] as String, + copyright: json['copyright'] as String? ?? '', + numOfEps: (json['numOfEps'] as num?)?.toInt() ?? 0, +); + +Map _$PodcastToJson(_Podcast instance) => { + '_id': instance.id, + 'feedUrl': instance.feedUrl, + 'podcastLink': instance.podcastLink, + 'title': instance.title, + 'description': instance.description, + 'imageLink': instance.imageLink, + 'copyright': instance.copyright, + 'numOfEps': instance.numOfEps, +}; diff --git a/mobile-app-new/lib/podcasts/repositories/api_repository.dart b/mobile-app-new/lib/podcasts/repositories/api_repository.dart new file mode 100644 index 000000000..862193886 --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/api_repository.dart @@ -0,0 +1,54 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mobile_app_new/services/dio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'api_repository.g.dart'; + +typedef RawEpisodesPage = ({ + Map podcast, + List> episodes, +}); + +const _prodUrl = 'https://api.mobile.freecodecamp.org'; +const _devUrl = 'https://api.mobile.freecodecamp.dev'; + +@riverpod +PodcastApiRepository podcastApiRepository(Ref ref) => PodcastApiRepository(); + +class PodcastApiRepository { + final Dio _dio = DioService.dio; + + String get _baseUrl => + dotenv.getBool('DEVELOPMENT_MODE', fallback: false) ? _devUrl : _prodUrl; + + Future>> getPodcasts() async { + final response = await _dio.get>('$_baseUrl/podcasts'); + + return [ + for (final entry in response.data ?? const []) + if (entry is Map) Map.from(entry), + ]; + } + + Future getEpisodes(String podcastId, {int page = 0}) async { + final response = await _dio.get>( + '$_baseUrl/podcasts/$podcastId/episodes', + queryParameters: {'page': page}, + ); + + final data = response.data ?? const {}; + final podcast = data['podcast']; + final episodes = data['episodes']; + + return ( + podcast: podcast is Map + ? Map.from(podcast) + : {}, + episodes: [ + for (final entry in episodes is List ? episodes : const []) + if (entry is Map) Map.from(entry), + ], + ); + } +} diff --git a/mobile-app-new/lib/podcasts/repositories/api_repository.g.dart b/mobile-app-new/lib/podcasts/repositories/api_repository.g.dart new file mode 100644 index 000000000..1faa71807 --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/api_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastApiRepository) +final podcastApiRepositoryProvider = PodcastApiRepositoryProvider._(); + +final class PodcastApiRepositoryProvider + extends + $FunctionalProvider< + PodcastApiRepository, + PodcastApiRepository, + PodcastApiRepository + > + with $Provider { + PodcastApiRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastApiRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastApiRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + PodcastApiRepository create(Ref ref) { + return podcastApiRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastApiRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastApiRepositoryHash() => + r'7ccb4cf53f8bcdf719d87a236334120d5405afa9'; diff --git a/mobile-app-new/lib/podcasts/repositories/download_repository.dart b/mobile-app-new/lib/podcasts/repositories/download_repository.dart new file mode 100644 index 000000000..66579d630 --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/download_repository.dart @@ -0,0 +1,200 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:mobile_app_new/services/dio_service.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'download_repository.g.dart'; + +// NOTE: Folder structure +// ApplicationDocumentsDirectory/ +// episodes/ +// / +// .mp3 +// images/ +// podcast/ +// .jpg + +const _episodesDirName = 'episodes'; +const _artworkDirName = 'podcast'; + +@riverpod +PodcastDownloadRepository podcastDownloadRepository(Ref ref) => + PodcastDownloadRepository(); + +class PodcastDownloadRepository { + final Dio _dio = DioService.dio; + + Directory? _documents; + + Future _documentsDir() async { + final cached = _documents; + if (cached != null) return cached; + + return _documents = await getApplicationDocumentsDirectory(); + } + + Future _episodesRoot() async => + Directory(path.join((await _documentsDir()).path, _episodesDirName)); + + Future _artworkRoot() async => Directory( + path.join((await _documentsDir()).path, 'images', _artworkDirName), + ); + + Future episodeFile(String podcastId, String episodeId) async => File( + path.join((await _episodesRoot()).path, podcastId, '$episodeId.mp3'), + ); + + Future artworkFile(String podcastId) async => + File(path.join((await _artworkRoot()).path, '$podcastId.jpg')); + + Future> existingArtworkFiles( + Iterable podcastIds, + ) async { + final files = {}; + + for (final podcastId in podcastIds) { + final file = await artworkFile(podcastId); + if (await file.exists()) files[podcastId] = file; + } + + return files; + } + + Future downloadEpisode({ + required String podcastId, + required String episodeId, + required String url, + CancelToken? cancelToken, + void Function(double progress)? onProgress, + }) async { + final file = await episodeFile(podcastId, episodeId); + await file.parent.create(recursive: true); + + await _downloadTo( + file, + url, + cancelToken: cancelToken, + onProgress: onProgress, + ); + } + + Future saveArtwork(String podcastId, String imageUrl) async { + if (imageUrl.isEmpty) return; + + final file = await artworkFile(podcastId); + if (await file.exists()) return; + + await file.parent.create(recursive: true); + + try { + await _downloadTo(file, imageUrl); + } catch (e) { + log('Podcast artwork: cannot fetch for $podcastId ($e)'); + } + } + + Future _downloadTo( + File destination, + String url, { + CancelToken? cancelToken, + void Function(double progress)? onProgress, + }) async { + await _dio.download( + url, + destination.path, + cancelToken: cancelToken, + deleteOnError: true, + onReceiveProgress: (received, total) { + if (total > 0) onProgress?.call(received / total); + }, + ); + } + + Future deleteEpisodeFile(String podcastId, String episodeId) async { + final file = await episodeFile(podcastId, episodeId); + if (await file.exists()) await file.delete(); + } + + Future deleteArtwork(String podcastId) async { + final file = await artworkFile(podcastId); + if (await file.exists()) await file.delete(); + } + + Future<({int count, int bytes})> deleteOrphans( + Map> expectedEpisodeIdsByPodcastId, + ) async { + final expected = {}; + + for (final MapEntry(key: podcastId, value: episodeIds) + in expectedEpisodeIdsByPodcastId.entries) { + expected.add((await artworkFile(podcastId)).path); + + for (final episodeId in episodeIds) { + expected.add((await episodeFile(podcastId, episodeId)).path); + } + } + + final orphans = [ + for (final root in [_episodesRoot(), _artworkRoot()]) + for (final file in await _listFiles(root)) + if (!expected.contains(file.path)) file, + ]; + + if (orphans.isEmpty) return (count: 0, bytes: 0); + + final bytes = await _deleteFiles(orphans); + await _removeEmptyPodcastDirectories(); + + return (count: orphans.length, bytes: bytes); + } + + Future> _listFiles(Future root) async { + final directory = await root; + if (!await directory.exists()) return []; + + try { + return await directory + .list(recursive: true) + .where((entity) => entity is File) + .cast() + .toList(); + } catch (e) { + log('Podcast cleanup: cannot list ${directory.path} ($e)'); + rethrow; + } + } + + Future _deleteFiles(Iterable files) async { + var bytes = 0; + + for (final file in files) { + try { + bytes += await file.length(); + await file.delete(); + } catch (e) { + log('Podcast cleanup: cannot delete ${file.path} ($e)'); + } + } + + return bytes; + } + + Future _removeEmptyPodcastDirectories() async { + final root = await _episodesRoot(); + if (!await root.exists()) return; + + await for (final entry in root.list()) { + if (entry is! Directory) continue; + + try { + if (await entry.list().isEmpty) await entry.delete(); + } catch (e) { + log('Podcast cleanup: cannot remove ${entry.path} ($e)'); + } + } + } +} diff --git a/mobile-app-new/lib/podcasts/repositories/download_repository.g.dart b/mobile-app-new/lib/podcasts/repositories/download_repository.g.dart new file mode 100644 index 000000000..ac58d6c59 --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/download_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'download_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastDownloadRepository) +final podcastDownloadRepositoryProvider = PodcastDownloadRepositoryProvider._(); + +final class PodcastDownloadRepositoryProvider + extends + $FunctionalProvider< + PodcastDownloadRepository, + PodcastDownloadRepository, + PodcastDownloadRepository + > + with $Provider { + PodcastDownloadRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastDownloadRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastDownloadRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + PodcastDownloadRepository create(Ref ref) { + return podcastDownloadRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastDownloadRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastDownloadRepositoryHash() => + r'15405ac28c4d7d71680f781d534431e409120a6b'; diff --git a/mobile-app-new/lib/podcasts/repositories/progress_repository.dart b/mobile-app-new/lib/podcasts/repositories/progress_repository.dart new file mode 100644 index 000000000..13ed7242b --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/progress_repository.dart @@ -0,0 +1,38 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +part 'progress_repository.g.dart'; + +@riverpod +PodcastProgressRepository podcastProgressRepository(Ref ref) => + PodcastProgressRepository(); + +class PodcastProgressRepository { + static String _positionKey(String episodeId) => '${episodeId}_progress'; + + static const _recentKey = 'podcast_recent_episodes'; + + SharedPreferences? _prefs; + + Future _instance() async => + _prefs ??= await SharedPreferences.getInstance(); + + Future readPosition(String episodeId) async => + (await _instance()).getInt(_positionKey(episodeId)); + + Future writePosition(String episodeId, int seconds) async => + (await _instance()).setInt(_positionKey(episodeId), seconds); + + Future clearPositions(Iterable episodeIds) async { + final prefs = await _instance(); + for (final id in episodeIds) { + await prefs.remove(_positionKey(id)); + } + } + + Future> readRecentEpisodeIds() async => + (await _instance()).getStringList(_recentKey) ?? const []; + + Future writeRecentEpisodeIds(List ids) async => + (await _instance()).setStringList(_recentKey, ids); +} diff --git a/mobile-app-new/lib/podcasts/repositories/progress_repository.g.dart b/mobile-app-new/lib/podcasts/repositories/progress_repository.g.dart new file mode 100644 index 000000000..76149a679 --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/progress_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'progress_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastProgressRepository) +final podcastProgressRepositoryProvider = PodcastProgressRepositoryProvider._(); + +final class PodcastProgressRepositoryProvider + extends + $FunctionalProvider< + PodcastProgressRepository, + PodcastProgressRepository, + PodcastProgressRepository + > + with $Provider { + PodcastProgressRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastProgressRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastProgressRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + PodcastProgressRepository create(Ref ref) { + return podcastProgressRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastProgressRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastProgressRepositoryHash() => + r'a8f9cbfc928b77ef4a7c73639a57ba9dfa9628c1'; diff --git a/mobile-app-new/lib/podcasts/repositories/storage_repository.dart b/mobile-app-new/lib/podcasts/repositories/storage_repository.dart new file mode 100644 index 000000000..f3cf61b5f --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/storage_repository.dart @@ -0,0 +1,282 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'storage_repository.g.dart'; + +typedef PodcastStoreEntry = Map; + +typedef StoredPodcast = ({ + PodcastStoreEntry podcast, + List episodes, +}); + +// `readable` is false when the file is absent or could not be parsed. The +// orphan sweep must not treat either as "no downloads" +typedef PodcastStoreSnapshot = ({List podcasts, bool readable}); + +typedef PodcastStoreIds = ({ + Map> episodeIdsByPodcastId, + bool readable, +}); + +const _fileName = 'podcast-downloads.json'; + +// Past versions +// 1 - { + 'id': '_id', + 'url': 'feedUrl', + 'link': 'podcastLink', + 'image': 'imageLink', + 'numEps': 'numOfEps', +}; + +const _legacyEpisodeKeys = { + 'id': '_id', + 'contentUrl': 'audioUrl', +}; + +const _episodesKey = 'episodes'; +const _idKey = '_id'; + +@riverpod +PodcastStorageRepository podcastStorageRepository(Ref ref) => + PodcastStorageRepository(); + +class PodcastStorageRepository { + File? _file; + + Future _resolveFile() async { + final baseDir = await getApplicationDocumentsDirectory(); + return _file ??= File(path.join(baseDir.path, 'storage', _fileName)); + } + + Future readPodcasts() => _serialized(_read); + + Future readIds() => _serialized(_readIds); + + Future containsEpisode({ + required String podcastId, + required String episodeId, + }) { + return _serialized(() async { + for (final row in (await _read()).podcasts) { + if (row.podcast[_idKey] != podcastId) continue; + + return row.episodes.any((raw) => raw[_idKey] == episodeId); + } + + return false; + }); + } + + Future upsertEpisode({ + required PodcastStoreEntry podcast, + required PodcastStoreEntry episode, + }) { + final podcastId = podcast[_idKey]; + final episodeId = episode[_idKey]; + + return _mutate((podcasts) { + final index = podcasts.indexWhere( + (row) => row.podcast[_idKey] == podcastId, + ); + + final episodes = index == -1 + ? [] + : List.from(podcasts[index].episodes); + + episodes + ..removeWhere((raw) => raw[_idKey] == episodeId) + ..add(episode); + + final row = (podcast: podcast, episodes: episodes); + + if (index == -1) { + podcasts.add(row); + } else { + podcasts[index] = row; + } + }); + } + + // NOTE: Returns whether that was the podcast's last episode + Future removeEpisode({ + required String podcastId, + required String episodeId, + }) { + return _mutate((podcasts) { + final index = podcasts.indexWhere( + (row) => row.podcast[_idKey] == podcastId, + ); + if (index == -1) return false; + + final episodes = List.from(podcasts[index].episodes) + ..removeWhere((raw) => raw[_idKey] == episodeId); + + if (episodes.isEmpty) { + podcasts.removeAt(index); + return true; + } + + podcasts[index] = (podcast: podcasts[index].podcast, episodes: episodes); + return false; + }); + } + + Future _read() async { + final entries = await _decode(); + if (entries == null) { + return (podcasts: const [], readable: false); + } + + return ( + podcasts: [for (final entry in entries) _split(entry)], + readable: true, + ); + } + + Future?> _decode() async { + final file = await _resolveFile(); + if (!await file.exists()) return null; + + try { + final decoded = jsonDecode(await file.readAsString()); + if (decoded is Map && decoded['podcasts'] is List) { + final isLegacy = decoded['version'] != _storeVersion; + + return [ + for (final entry in decoded['podcasts'] as List) + if (entry is Map) + isLegacy + ? _upgradeToV2(PodcastStoreEntry.from(entry)) + : PodcastStoreEntry.from(entry), + ]; + } + log('Podcast downloads: unexpected shape in ${file.path}'); + } catch (e) { + log('Podcast downloads: cannot read ${file.path} ($e)'); + } + + return null; + } + + Future _readIds() async { + const empty = >{}; + + final snapshot = await _read(); + if (!snapshot.readable) { + return (episodeIdsByPodcastId: empty, readable: false); + } + + final ids = >{}; + + for (final row in snapshot.podcasts) { + final podcastId = row.podcast[_idKey]; + if (podcastId is! String || podcastId.isEmpty) { + log('Podcast downloads: row without an id'); + return (episodeIdsByPodcastId: empty, readable: false); + } + + final episodeIds = {}; + + for (final raw in row.episodes) { + final episodeId = raw[_idKey]; + if (episodeId is! String || episodeId.isEmpty) { + log('Podcast downloads: episode without an id'); + return (episodeIdsByPodcastId: empty, readable: false); + } + + episodeIds.add(episodeId); + } + + ids[podcastId] = episodeIds; + } + + return (episodeIdsByPodcastId: ids, readable: true); + } + + StoredPodcast _split(PodcastStoreEntry entry) { + final episodes = entry[_episodesKey]; + + return ( + podcast: {...entry}..remove(_episodesKey), + episodes: [ + for (final raw in episodes is List ? episodes : const []) + if (raw is Map) PodcastStoreEntry.from(raw), + ], + ); + } + + PodcastStoreEntry _join(StoredPodcast row) => { + ...row.podcast, + _episodesKey: row.episodes, + }; + + PodcastStoreEntry _upgradeToV2(PodcastStoreEntry entry) { + final episodes = entry[_episodesKey]; + + return { + ..._renameKeys(entry, _legacyPodcastKeys), + if (episodes is List) + _episodesKey: [ + for (final raw in episodes) + if (raw is Map) + _renameKeys(PodcastStoreEntry.from(raw), _legacyEpisodeKeys), + ], + }; + } + + PodcastStoreEntry _renameKeys( + PodcastStoreEntry entry, + Map renames, + ) { + return { + for (final MapEntry(:key, :value) in entry.entries) + (renames[key] ?? key): value, + }; + } + + Future _mutate(R Function(List podcasts) updater) { + return _serialized(() async { + final podcasts = List.from((await _read()).podcasts); + final result = updater(podcasts); + + await _write(podcasts); + + return result; + }); + } + + Future _write(List podcasts) async { + final file = await _resolveFile(); + await file.parent.create(recursive: true); + + final temp = File('${file.path}.tmp'); + await temp.writeAsString( + jsonEncode({ + 'version': _storeVersion, + 'podcasts': [for (final row in podcasts) _join(row)], + }), + flush: true, + ); + await temp.rename(file.path); + } + + Future _tail = Future.value(); + + Future _serialized(Future Function() action) { + final next = _tail.then((_) => action()); + _tail = next.then((_) {}, onError: (_) {}); + return next; + } +} diff --git a/mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart b/mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart new file mode 100644 index 000000000..60470e597 --- /dev/null +++ b/mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'storage_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastStorageRepository) +final podcastStorageRepositoryProvider = PodcastStorageRepositoryProvider._(); + +final class PodcastStorageRepositoryProvider + extends + $FunctionalProvider< + PodcastStorageRepository, + PodcastStorageRepository, + PodcastStorageRepository + > + with $Provider { + PodcastStorageRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastStorageRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastStorageRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + PodcastStorageRepository create(Ref ref) { + return podcastStorageRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastStorageRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastStorageRepositoryHash() => + r'bda3ac43ee0a8ebfa3993c374904e090669f8d59'; diff --git a/mobile-app-new/lib/podcasts/services/api_service.dart b/mobile-app-new/lib/podcasts/services/api_service.dart new file mode 100644 index 000000000..7f48119e1 --- /dev/null +++ b/mobile-app-new/lib/podcasts/services/api_service.dart @@ -0,0 +1,68 @@ +import 'dart:developer'; + +import 'package:mobile_app_new/podcasts/constants.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/repositories/api_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'api_service.g.dart'; + +typedef EpisodesPage = ({ + Podcast podcast, + List episodes, + bool hasMore, +}); + +const _episodesPerPage = 20; + +@riverpod +PodcastApiService podcastApiService(Ref ref) { + final repo = ref.watch(podcastApiRepositoryProvider); + return PodcastApiService(repo); +} + +class PodcastApiService { + final PodcastApiRepository _repo; + + PodcastApiService(this._repo); + + Future> getPodcasts() async { + final podcasts = [ + for (final raw in await _repo.getPodcasts()) ?_toPodcast(raw), + ]; + + return [ + ...podcasts.where((podcast) => fccPodcastUrls.contains(podcast.feedUrl)), + ...podcasts.where((podcast) => !fccPodcastUrls.contains(podcast.feedUrl)), + ]; + } + + Podcast? _toPodcast(Map raw) { + try { + return Podcast.fromJson(raw); + } catch (e) { + log('Podcast ${raw['_id']} is missing required fields; skipping ($e)'); + return null; + } + } + + Future getEpisodes(String podcastId, {int page = 0}) async { + final raw = await _repo.getEpisodes(podcastId, page: page); + + final playable = raw.episodes.where((episode) { + final url = episode['audioUrl']; + if (url is String && url.isNotEmpty) return true; + + // TODO: Capture in Crashlytics so we can fix by adding the missing audioUrl to the API + log('Podcast episode ${episode['_id']} has no audioUrl; skipping'); + return false; + }); + + return ( + podcast: Podcast.fromJson(raw.podcast), + episodes: playable.map(Episode.fromJson).toList(), + hasMore: raw.episodes.length >= _episodesPerPage, + ); + } +} diff --git a/mobile-app-new/lib/podcasts/services/api_service.g.dart b/mobile-app-new/lib/podcasts/services/api_service.g.dart new file mode 100644 index 000000000..a85a7947b --- /dev/null +++ b/mobile-app-new/lib/podcasts/services/api_service.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastApiService) +final podcastApiServiceProvider = PodcastApiServiceProvider._(); + +final class PodcastApiServiceProvider + extends + $FunctionalProvider< + PodcastApiService, + PodcastApiService, + PodcastApiService + > + with $Provider { + PodcastApiServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastApiServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastApiServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + PodcastApiService create(Ref ref) { + return podcastApiService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastApiService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastApiServiceHash() => r'b94a43282e9a2601f8863cbf964d9f7bbd827535'; diff --git a/mobile-app-new/lib/podcasts/services/library_service.dart b/mobile-app-new/lib/podcasts/services/library_service.dart new file mode 100644 index 000000000..4b38c6943 --- /dev/null +++ b/mobile-app-new/lib/podcasts/services/library_service.dart @@ -0,0 +1,142 @@ +import 'dart:developer'; + +import 'dart:io'; + +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:dio/dio.dart'; +import 'package:mobile_app_new/podcasts/repositories/download_repository.dart'; +import 'package:mobile_app_new/podcasts/repositories/storage_repository.dart'; +import 'package:mobile_app_new/services/notification_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'library_service.g.dart'; + +typedef PodcastLibrary = ({ + List podcasts, + Map> episodesByPodcastId, + Map artworkByPodcastId, +}); + +@riverpod +PodcastLibraryService podcastLibraryService(Ref ref) { + return PodcastLibraryService( + ref.watch(podcastStorageRepositoryProvider), + ref.watch(podcastDownloadRepositoryProvider), + ref.watch(notificationServiceProvider), + ); +} + +class PodcastLibraryService { + PodcastLibraryService(this._repo, this._files, this._notifications); + + final PodcastStorageRepository _repo; + final PodcastDownloadRepository _files; + final NotificationService _notifications; + + Future download( + Podcast podcast, + Episode episode, { + CancelToken? cancelToken, + void Function(double progress)? onProgress, + }) async { + await _files.downloadEpisode( + podcastId: episode.podcastId, + episodeId: episode.id, + url: episode.audioUrl, + cancelToken: cancelToken, + onProgress: onProgress, + ); + + await _files.saveArtwork(podcast.id, podcast.imageLink); + + await _repo.upsertEpisode( + podcast: podcast.toJson(), + episode: episode.toJson(), + ); + + await _notifications.showNotification( + 'Download complete', + '${podcast.title} - ${episode.title}', + ); + } + + Future getLibrary() async { + final snapshot = await _repo.readPodcasts(); + + final podcasts = []; + final episodesByPodcastId = >{}; + + for (final row in snapshot.podcasts) { + final podcast = _toPodcast(row.podcast); + if (podcast == null) continue; + + final episodes = [for (final raw in row.episodes) ?_toEpisode(raw)]; + + podcasts.add(podcast); + episodesByPodcastId[podcast.id] = episodes; + } + + return ( + podcasts: podcasts, + episodesByPodcastId: episodesByPodcastId, + artworkByPodcastId: await _files.existingArtworkFiles( + podcasts.map((podcast) => podcast.id), + ), + ); + } + + Future removeEpisode(String podcastId, String episodeId) async { + final podcastEmptied = await _repo.removeEpisode( + podcastId: podcastId, + episodeId: episodeId, + ); + + await _files.deleteEpisodeFile(podcastId, episodeId); + if (podcastEmptied) await _files.deleteArtwork(podcastId); + } + + // NOTE: Deletes audio and artwork on disk that the store has no row for. + // Runs unattended at startup, so a failure here must stay contained. + Future sweepOrphanedFiles() async { + try { + final store = await _repo.readIds(); + + if (!store.readable) { + log('Podcast cleanup: store unreadable or incomplete, skipping sweep'); + return; + } + + final removed = await _files.deleteOrphans(store.episodeIdsByPodcastId); + if (removed.count == 0) return; + + log( + 'Podcast cleanup: removed ${removed.count} orphaned files ' + '(${(removed.bytes / 1024 / 1024).toStringAsFixed(1)} MB)', + ); + } catch (e) { + log('Podcast cleanup: sweep failed ($e)'); + } + } + + // Tolerant by design: this is user data that predates v8, and one unreadable + // row should cost that row rather than the whole downloads list. The models + // throw on a missing required field, so the skip happens here. + Podcast? _toPodcast(PodcastStoreEntry entry) { + try { + return Podcast.fromJson(entry); + } catch (e) { + log('Podcast downloads: skipping unreadable podcast row ($e)'); + return null; + } + } + + Episode? _toEpisode(Map raw) { + try { + return Episode.fromJson(raw); + } catch (e) { + log('Podcast downloads: skipping unreadable episode row ($e)'); + return null; + } + } +} diff --git a/mobile-app-new/lib/podcasts/services/library_service.g.dart b/mobile-app-new/lib/podcasts/services/library_service.g.dart new file mode 100644 index 000000000..9d1c1e59c --- /dev/null +++ b/mobile-app-new/lib/podcasts/services/library_service.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'library_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastLibraryService) +final podcastLibraryServiceProvider = PodcastLibraryServiceProvider._(); + +final class PodcastLibraryServiceProvider + extends + $FunctionalProvider< + PodcastLibraryService, + PodcastLibraryService, + PodcastLibraryService + > + with $Provider { + PodcastLibraryServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastLibraryServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastLibraryServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + PodcastLibraryService create(Ref ref) { + return podcastLibraryService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastLibraryService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastLibraryServiceHash() => + r'aea6f426a719db3df28934b955b43ca51c7c041d'; diff --git a/mobile-app-new/lib/podcasts/services/playback_service.dart b/mobile-app-new/lib/podcasts/services/playback_service.dart new file mode 100644 index 000000000..f21bc0a2a --- /dev/null +++ b/mobile-app-new/lib/podcasts/services/playback_service.dart @@ -0,0 +1,196 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:audio_service/audio_service.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/repositories/download_repository.dart'; +import 'package:mobile_app_new/podcasts/repositories/progress_repository.dart'; +import 'package:mobile_app_new/podcasts/repositories/storage_repository.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'playback_service.g.dart'; + +typedef EpisodePlaybackUris = ({Uri audio, Uri? artwork}); + +const _writeInterval = Duration(seconds: 5); + +const _recentEpisodeLimit = 10; + +// NOTE: Below this, an episode restarts rather than spending a slot on a tap. +const _minimumSavedPosition = Duration(seconds: 30); + +// NOTE: Above this, an episode is considered complete and the position is cleared. +const _completionMargin = Duration(seconds: 30); + +@riverpod +PodcastPlaybackService podcastPlaybackService(Ref ref) { + final service = PodcastPlaybackService( + ref.watch(audioHandlerProvider), + ref.watch(podcastProgressRepositoryProvider), + ref.watch(podcastStorageRepositoryProvider), + ref.watch(podcastDownloadRepositoryProvider), + ); + + ref.onDispose(service.dispose); + service.start(); + + return service; +} + +class PodcastPlaybackService { + PodcastPlaybackService(this._handler, this._repo, this._store, this._files); + + final AudioPlayerHandler _handler; + final PodcastProgressRepository _repo; + final PodcastStorageRepository _store; + final PodcastDownloadRepository _files; + + StreamSubscription? _positionSub; + StreamSubscription? _stateSub; + + String? _episodeId; + Duration _position = Duration.zero; + Duration? _duration; + Duration _lastWritten = Duration.zero; + bool _wasPlaying = false; + + void start() { + _positionSub ??= _handler.positionStream.listen(_onPosition); + _stateSub ??= _handler.playbackState.listen(_onPlaybackState); + } + + Future readPosition(String episodeId) async { + final seconds = await _repo.readPosition(episodeId); + return seconds == null ? null : Duration(seconds: seconds); + } + + Future playbackUris( + Podcast podcast, + Episode episode, + ) async { + final isDownloaded = await _store.containsEpisode( + podcastId: episode.podcastId, + episodeId: episode.id, + ); + + return ( + audio: await _audioSource(episode, isDownloaded: isDownloaded), + artwork: await _artworkSource(podcast, isDownloaded: isDownloaded), + ); + } + + Future _audioSource( + Episode episode, { + required bool isDownloaded, + }) async { + if (isDownloaded) { + final file = await _files.episodeFile(episode.podcastId, episode.id); + + if (await file.exists()) return file.uri; + + log( + 'Podcast playback: ${episode.id} marked downloaded but file is missing', + ); + } + + return Uri.parse(episode.audioUrl); + } + + Future _artworkSource( + Podcast podcast, { + required bool isDownloaded, + }) async { + if (isDownloaded) { + final file = await _files.artworkFile(podcast.id); + + if (await file.exists()) return file.uri; + } + + return podcast.imageLink.isEmpty ? null : Uri.parse(podcast.imageLink); + } + + void _onPosition(Duration position) { + final episodeId = _handler.episodeId; + + if (episodeId == null) return; + + if (episodeId != _episodeId) { + _episodeId = episodeId; + _lastWritten = Duration.zero; + } + + _position = position; + _duration = _handler.duration; + + if ((position - _lastWritten).abs() < _writeInterval) return; + + _lastWritten = position; + unawaited(_savePosition(episodeId, position, _duration)); + } + + void _onPlaybackState(PlaybackState state) { + final wasPlaying = _wasPlaying; + _wasPlaying = state.playing; + + if (!wasPlaying || state.playing) return; + + final episodeId = _episodeId; + if (episodeId == null) return; + + _lastWritten = _position; + unawaited(_savePosition(episodeId, _position, _duration)); + } + + Future _savePosition( + String episodeId, + Duration position, + Duration? total, + ) async { + if (total != null && + total > Duration.zero && + position >= total - _completionMargin) { + return _clearPosition(episodeId); + } + + if (position < _minimumSavedPosition) return; + + await _repo.writePosition(episodeId, position.inSeconds); + await _markRecentlyPlayed(episodeId); + } + + Future _clearPosition(String episodeId) async { + await _repo.clearPositions([episodeId]); + + final recent = await _repo.readRecentEpisodeIds(); + if (!recent.contains(episodeId)) return; + + await _repo.writeRecentEpisodeIds( + recent.where((id) => id != episodeId).toList(), + ); + } + + Future _markRecentlyPlayed(String episodeId) async { + final recent = await _repo.readRecentEpisodeIds(); + + if (recent.isNotEmpty && recent.last == episodeId) return; + + final updated = [...recent.where((id) => id != episodeId), episodeId]; + + if (updated.length > _recentEpisodeLimit) { + final evicted = updated.sublist(0, updated.length - _recentEpisodeLimit); + await _repo.clearPositions(evicted); + updated.removeRange(0, evicted.length); + } + + await _repo.writeRecentEpisodeIds(updated); + } + + void dispose() { + _positionSub?.cancel(); + _stateSub?.cancel(); + _positionSub = null; + _stateSub = null; + } +} diff --git a/mobile-app-new/lib/podcasts/services/playback_service.g.dart b/mobile-app-new/lib/podcasts/services/playback_service.g.dart new file mode 100644 index 000000000..3c06f33d4 --- /dev/null +++ b/mobile-app-new/lib/podcasts/services/playback_service.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'playback_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastPlaybackService) +final podcastPlaybackServiceProvider = PodcastPlaybackServiceProvider._(); + +final class PodcastPlaybackServiceProvider + extends + $FunctionalProvider< + PodcastPlaybackService, + PodcastPlaybackService, + PodcastPlaybackService + > + with $Provider { + PodcastPlaybackServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastPlaybackServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastPlaybackServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + PodcastPlaybackService create(Ref ref) { + return podcastPlaybackService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PodcastPlaybackService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$podcastPlaybackServiceHash() => + r'fdba54b5176077af6f9dafafb320aa44c3ef7223'; diff --git a/mobile-app-new/lib/podcasts/ui/downloads/downloads_view.dart b/mobile-app-new/lib/podcasts/ui/downloads/downloads_view.dart new file mode 100644 index 000000000..2cee519b7 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/downloads/downloads_view.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/controllers/library_controller.dart'; +import 'package:mobile_app_new/podcasts/ui/widgets/podcast_grid_tile.dart'; +import 'package:mobile_app_new/routing/podcasts.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +class PodcastDownloadsView extends ConsumerWidget { + const PodcastDownloadsView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final library = ref.watch(podcastLibraryProvider); + + return ColoredBox( + color: FccColors.gray80, + child: library.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => ErrorRetry( + message: 'Unable to load downloads.', + onRetry: () => ref.invalidate(podcastLibraryProvider), + ), + data: (library) => library.podcasts.isEmpty + ? const _NoDownloads() + : GridView.count( + crossAxisCount: 2, + childAspectRatio: 1, + children: [ + for (final podcast in library.podcasts) + PodcastGridTile( + key: ValueKey(podcast.id), + podcast: podcast, + artwork: library.artworkByPodcastId[podcast.id], + onTap: () => PodcastEpisodeListRoute( + podcastId: podcast.id, + downloaded: true, + ).push(context), + ), + ], + ), + ), + ); + } +} + +class _NoDownloads extends StatelessWidget { + const _NoDownloads(); + + @override + Widget build(BuildContext context) { + return const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.arrow_circle_down_sharp, color: Colors.white, size: 50), + SizedBox(height: 4), + Text( + 'No downloads', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/podcasts/ui/episode/episode_view.dart b/mobile-app-new/lib/podcasts/ui/episode/episode_view.dart new file mode 100644 index 000000000..3c1134e2c --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/episode/episode_view.dart @@ -0,0 +1,279 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/constants.dart'; +import 'package:mobile_app_new/podcasts/controllers/player_controller.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/ui/widgets/download_button.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; + +class PodcastEpisodeView extends StatefulWidget { + const PodcastEpisodeView({ + super.key, + required this.podcast, + required this.episode, + }); + + final Podcast podcast; + final Episode episode; + + @override + State createState() => _PodcastEpisodeViewState(); +} + +class _PodcastEpisodeViewState extends State { + List? _description; + + bool _isScrubbing = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: ListView( + physics: _isScrubbing ? const NeverScrollableScrollPhysics() : null, + children: [ + _Artwork(imageUrl: widget.podcast.imageLink), + Padding( + padding: const EdgeInsets.all(8), + child: Text( + widget.episode.title, + textAlign: TextAlign.center, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + ), + Text( + widget.podcast.title, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 16), + ), + _Scrubber( + episode: widget.episode, + onScrubbingChanged: (value) => setState(() => _isScrubbing = value), + ), + _Controls(podcast: widget.podcast, episode: widget.episode), + Padding( + padding: const EdgeInsets.fromLTRB(8, 16, 8, 8), + child: Column(children: _parsedDescription()), + ), + ], + ), + ); + } + + List _parsedDescription() { + if (widget.episode.description.isEmpty) return const []; + + return _description ??= HTMLParser( + context: context, + ).parse('

${widget.episode.description}

'); + } +} + +class _Artwork extends StatelessWidget { + const _Artwork({required this.imageUrl}); + + final String imageUrl; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.4, + ), + child: AspectRatio( + aspectRatio: 1, + child: DecoratedBox( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.3), + blurRadius: 10, + spreadRadius: 5, + offset: const Offset(0, 3), + ), + ], + ), + child: imageUrl.isEmpty + ? const ColoredBox(color: FccColors.gray80) + : CachedNetworkImage(imageUrl: imageUrl, fit: BoxFit.cover), + ), + ), + ), + ); + } +} + +class _Scrubber extends ConsumerStatefulWidget { + const _Scrubber({required this.episode, required this.onScrubbingChanged}); + + final Episode episode; + final ValueChanged onScrubbingChanged; + + @override + ConsumerState<_Scrubber> createState() => _ScrubberState(); +} + +class _ScrubberState extends ConsumerState<_Scrubber> { + Duration? _scrubbing; + + static String _format(Duration value) { + final seconds = value.inSeconds; + final hours = seconds ~/ 3600; + final minutes = (seconds % 3600) ~/ 60; + final rest = seconds % 60; + String two(int n) => n.toString().padLeft(2, '0'); + + return hours > 0 + ? '$hours:${two(minutes)}:${two(rest)}' + : '$minutes:${two(rest)}'; + } + + @override + Widget build(BuildContext context) { + final player = ref.watch( + podcastPlayerProvider.select((state) { + final isCurrent = state.isCurrent(widget.episode.id); + + return ( + isCurrent: isCurrent, + duration: isCurrent + ? state.duration ?? widget.episode.duration + : widget.episode.duration, + ); + }), + ); + + final livePosition = player.isCurrent + ? ref.watch(podcastPlaybackPositionProvider).value ?? Duration.zero + : Duration.zero; + + final position = _scrubbing ?? livePosition; + final total = player.duration; + final isSeekable = total != null && total > Duration.zero; + final elapsed = isSeekable && position > total ? total : position; + + return Column( + children: [ + Listener( + onPointerDown: (_) => widget.onScrubbingChanged(true), + onPointerUp: (_) => widget.onScrubbingChanged(false), + onPointerCancel: (_) => widget.onScrubbingChanged(false), + child: Slider( + value: isSeekable + ? elapsed.inMilliseconds / total.inMilliseconds + : 0, + onChanged: isSeekable + ? (value) => setState(() => _scrubbing = total * value) + : null, + onChangeEnd: isSeekable + ? (value) { + ref + .read(podcastPlayerProvider.notifier) + .seek(total * value); + setState(() => _scrubbing = null); + } + : null, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(_format(elapsed), style: const TextStyle(fontSize: 16)), + Text( + isSeekable ? '-${_format(total - elapsed)}' : '--:--', + style: const TextStyle(fontSize: 16), + ), + ], + ), + ), + ], + ); + } +} + +class _Controls extends ConsumerWidget { + const _Controls({required this.podcast, required this.episode}); + + final Podcast podcast; + final Episode episode; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final status = ref.watch( + podcastPlayerProvider.select( + (state) => ( + isPlaying: state.isPlayingEpisode(episode.id), + isBusy: state.isBusyWith(episode.id), + speed: state.speed, + ), + ), + ); + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DropdownButtonHideUnderline( + child: DropdownButton( + value: podcastSpeedOptions.contains(status.speed) + ? status.speed + : 1.0, + dropdownColor: FccColors.gray90, + icon: const SizedBox.shrink(), + onChanged: (value) { + if (value != null) { + ref.read(podcastPlayerProvider.notifier).setSpeed(value); + } + }, + items: [ + for (final option in podcastSpeedOptions) + DropdownMenuItem( + value: option, + child: Text( + '${option}x', + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + ), + const SizedBox(width: 8), + IconButton( + iconSize: 45, + icon: const Icon(Icons.replay_10_rounded), + onPressed: () => ref.read(podcastPlayerProvider.notifier).rewind(), + ), + IconButton( + iconSize: 80, + onPressed: () => + ref.read(podcastPlayerProvider.notifier).toggle(podcast, episode), + icon: status.isBusy + ? const SizedBox.square( + dimension: 80, + child: Padding( + padding: EdgeInsets.all(8), + child: CircularProgressIndicator( + color: FccColors.gray00, + strokeWidth: 2, + ), + ), + ) + : Icon(status.isPlaying ? Icons.pause : Icons.play_arrow_rounded), + ), + IconButton( + iconSize: 45, + icon: const Icon(Icons.forward_30_rounded), + onPressed: () => + ref.read(podcastPlayerProvider.notifier).fastForward(), + ), + PodcastDownloadButton(podcast: podcast, episode: episode, size: 45), + ], + ); + } +} diff --git a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart new file mode 100644 index 000000000..6b0308138 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart @@ -0,0 +1,260 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/controllers/library_controller.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/ui/episode_list/episode_list_viewmodel.dart'; +import 'package:mobile_app_new/podcasts/ui/widgets/episode_tile.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class PodcastEpisodeListView extends ConsumerStatefulWidget { + const PodcastEpisodeListView({super.key, required this.source}); + + final PodcastEpisodeSource source; + + @override + ConsumerState createState() => + _PodcastEpisodeListViewState(); +} + +class _PodcastEpisodeListViewState + extends ConsumerState { + final _scrollController = ScrollController(); + bool _showFullDescription = false; + + PodcastEpisodesNotifierProvider get _provider => + podcastEpisodesProvider(widget.source); + + @override + void initState() { + super.initState(); + _scrollController.addListener(_onScroll); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _onScroll() { + if (_scrollController.position.pixels >= + _scrollController.position.maxScrollExtent - 1000) { + ref.read(_provider.notifier).fetchNextPage(); + } + } + + @override + Widget build(BuildContext context) { + final episodes = ref.watch(_provider); + + return Scaffold( + backgroundColor: FccColors.gray90, + appBar: AppBar(title: Text(episodes.value?.podcast.title ?? '')), + body: episodes.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => ErrorRetry( + message: 'Unable to load episodes.', + onRetry: () => ref.invalidate(_provider), + ), + data: (state) => RefreshIndicator( + onRefresh: () async => ref.invalidate(_provider), + backgroundColor: FccColors.gray90, + color: Colors.white, + child: CustomScrollView( + controller: _scrollController, + slivers: [ + SliverToBoxAdapter( + child: Column( + children: [ + _Header(podcast: state.podcast, source: widget.source), + _Description( + description: state.podcast.description, + isExpanded: _showFullDescription, + onToggle: () => setState( + () => _showFullDescription = !_showFullDescription, + ), + ), + ], + ), + ), + if (state.episodes.isEmpty) + const SliverFillRemaining( + hasScrollBody: false, + child: Center(child: Text('No episodes')), + ) + else + _buildList(state), + ], + ), + ), + ), + ); + } + + Widget _buildList(PodcastEpisodesState state) { + final footer = _buildFooter(state); + + return SliverList.separated( + itemCount: state.episodes.length + (footer == null ? 0 : 1), + separatorBuilder: (_, _) => + const Divider(color: FccColors.gray80, thickness: 1, height: 1), + itemBuilder: (context, index) => index == state.episodes.length + ? footer + : PodcastEpisodeTile( + key: ValueKey(state.episodes[index].id), + podcast: state.podcast, + episode: state.episodes[index], + ), + ); + } + + Widget? _buildFooter(PodcastEpisodesState state) { + if (state.loadMoreError != null) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: ErrorRetry( + message: 'Unable to load more episodes.', + onRetry: () => + ref.read(_provider.notifier).fetchNextPage(isRetry: true), + ), + ); + } + + if (state.isLoadingMore) { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 24), + child: Center(child: CircularProgressIndicator()), + ); + } + + return null; + } +} + +class _Header extends ConsumerWidget { + const _Header({required this.podcast, required this.source}); + + final Podcast podcast; + final PodcastEpisodeSource source; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Stack( + children: [ + SizedBox( + width: double.infinity, + child: AspectRatio(aspectRatio: 1, child: _artwork(ref)), + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: DecoratedBox( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.75), + spreadRadius: 1.5, + blurRadius: 5, + ), + ], + ), + child: Text( + podcast.title, + maxLines: 2, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 24, + height: 1.2, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ], + ); + } + + Widget _artwork(WidgetRef ref) { + if (source is DownloadedEpisodes) { + final artwork = ref.watch( + podcastLibraryProvider.select( + (state) => state.value?.artworkByPodcastId[podcast.id], + ), + ); + + if (artwork != null) return Image.file(artwork, fit: BoxFit.cover); + } + + if (podcast.imageLink.isEmpty) { + return const ColoredBox(color: FccColors.gray80); + } + + return CachedNetworkImage(imageUrl: podcast.imageLink, fit: BoxFit.cover); + } +} + +class _Description extends StatelessWidget { + const _Description({ + required this.description, + required this.isExpanded, + required this.onToggle, + }); + + final String description; + final bool isExpanded; + final VoidCallback onToggle; + + @override + Widget build(BuildContext context) { + if (description.isEmpty) return const SizedBox.shrink(); + + return ColoredBox( + color: FccColors.gray90, + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Description', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + height: 1.2, + ), + ), + Html( + data: description, + onLinkTap: (url, _, _) { + if (url != null) launchUrl(Uri.parse(url)); + }, + style: { + '#': Style( + fontSize: FontSize(16), + color: Colors.white.withValues(alpha: 0.87), + margin: Margins.zero, + maxLines: isExpanded ? null : 3, + ), + }, + ), + TextButton( + onPressed: onToggle, + style: TextButton.styleFrom( + shape: const RoundedRectangleBorder(), + ), + child: Text( + isExpanded ? 'Show less' : 'Show more', + style: const TextStyle(fontSize: 16), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart new file mode 100644 index 000000000..1dc2e14dc --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart @@ -0,0 +1,110 @@ +import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/podcasts/controllers/library_controller.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/services/api_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'episode_list_viewmodel.freezed.dart'; +part 'episode_list_viewmodel.g.dart'; + +@freezed +sealed class PodcastEpisodeSource with _$PodcastEpisodeSource { + const factory PodcastEpisodeSource.remote(String podcastId) = RemoteEpisodes; + const factory PodcastEpisodeSource.downloaded(String podcastId) = + DownloadedEpisodes; +} + +@freezed +abstract class PodcastEpisodesState with _$PodcastEpisodesState { + const factory PodcastEpisodesState({ + required Podcast podcast, + @Default([]) List episodes, + @Default(0) int page, + @Default(false) bool hasNextPage, + @Default(false) bool isLoadingMore, + // NOTE: Set when appending a page failed; the episodes already loaded stay valid. + Object? loadMoreError, + }) = _PodcastEpisodesState; +} + +@riverpod +class PodcastEpisodesNotifier extends _$PodcastEpisodesNotifier { + Podcast? _lastKnownPodcast; + + @override + FutureOr build(PodcastEpisodeSource source) async { + switch (source) { + case RemoteEpisodes(:final podcastId): + final page = await ref + .watch(podcastApiServiceProvider) + .getEpisodes(podcastId); + + _lastKnownPodcast = page.podcast; + + return PodcastEpisodesState( + podcast: page.podcast, + episodes: page.episodes, + hasNextPage: page.hasMore, + ); + + case DownloadedEpisodes(:final podcastId): + final library = await ref.watch(podcastLibraryProvider.future); + + final podcast = + library.podcasts.firstWhereOrNull( + (podcast) => podcast.id == podcastId, + ) ?? + _lastKnownPodcast; + + if (podcast == null) { + throw StateError('Podcast $podcastId has no downloaded episodes'); + } + + _lastKnownPodcast = podcast; + + return PodcastEpisodesState( + podcast: podcast, + episodes: library.episodesByPodcastId[podcastId] ?? const [], + ); + } + } + + Future fetchNextPage({bool isRetry = false}) async { + final current = state.value; + if (current == null || !current.hasNextPage || current.isLoadingMore) { + return; + } + + // A failed append waits for an explicit retry instead of refiring on scroll. + if (current.loadMoreError != null && !isRetry) return; + + if (source case RemoteEpisodes(:final podcastId)) { + state = AsyncData( + current.copyWith(isLoadingMore: true, loadMoreError: null), + ); + + final next = current.page + 1; + + try { + final page = await ref + .read(podcastApiServiceProvider) + .getEpisodes(podcastId, page: next); + + state = AsyncData( + current.copyWith( + episodes: [...current.episodes, ...page.episodes], + page: next, + hasNextPage: page.hasMore, + isLoadingMore: false, + ), + ); + } catch (error) { + state = AsyncData( + current.copyWith(isLoadingMore: false, loadMoreError: error), + ); + } + } + } +} diff --git a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.freezed.dart b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.freezed.dart new file mode 100644 index 000000000..eda5fe43e --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.freezed.dart @@ -0,0 +1,631 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'episode_list_viewmodel.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$PodcastEpisodeSource { + + String get podcastId; +/// Create a copy of PodcastEpisodeSource +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PodcastEpisodeSourceCopyWith get copyWith => _$PodcastEpisodeSourceCopyWithImpl(this as PodcastEpisodeSource, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PodcastEpisodeSource&&(identical(other.podcastId, podcastId) || other.podcastId == podcastId)); +} + + +@override +int get hashCode => Object.hash(runtimeType,podcastId); + +@override +String toString() { + return 'PodcastEpisodeSource(podcastId: $podcastId)'; +} + + +} + +/// @nodoc +abstract mixin class $PodcastEpisodeSourceCopyWith<$Res> { + factory $PodcastEpisodeSourceCopyWith(PodcastEpisodeSource value, $Res Function(PodcastEpisodeSource) _then) = _$PodcastEpisodeSourceCopyWithImpl; +@useResult +$Res call({ + String podcastId +}); + + + + +} +/// @nodoc +class _$PodcastEpisodeSourceCopyWithImpl<$Res> + implements $PodcastEpisodeSourceCopyWith<$Res> { + _$PodcastEpisodeSourceCopyWithImpl(this._self, this._then); + + final PodcastEpisodeSource _self; + final $Res Function(PodcastEpisodeSource) _then; + +/// Create a copy of PodcastEpisodeSource +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? podcastId = null,}) { + return _then(_self.copyWith( +podcastId: null == podcastId ? _self.podcastId : podcastId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PodcastEpisodeSource]. +extension PodcastEpisodeSourcePatterns on PodcastEpisodeSource { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( RemoteEpisodes value)? remote,TResult Function( DownloadedEpisodes value)? downloaded,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case RemoteEpisodes() when remote != null: +return remote(_that);case DownloadedEpisodes() when downloaded != null: +return downloaded(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( RemoteEpisodes value) remote,required TResult Function( DownloadedEpisodes value) downloaded,}){ +final _that = this; +switch (_that) { +case RemoteEpisodes(): +return remote(_that);case DownloadedEpisodes(): +return downloaded(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( RemoteEpisodes value)? remote,TResult? Function( DownloadedEpisodes value)? downloaded,}){ +final _that = this; +switch (_that) { +case RemoteEpisodes() when remote != null: +return remote(_that);case DownloadedEpisodes() when downloaded != null: +return downloaded(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( String podcastId)? remote,TResult Function( String podcastId)? downloaded,required TResult orElse(),}) {final _that = this; +switch (_that) { +case RemoteEpisodes() when remote != null: +return remote(_that.podcastId);case DownloadedEpisodes() when downloaded != null: +return downloaded(_that.podcastId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( String podcastId) remote,required TResult Function( String podcastId) downloaded,}) {final _that = this; +switch (_that) { +case RemoteEpisodes(): +return remote(_that.podcastId);case DownloadedEpisodes(): +return downloaded(_that.podcastId);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( String podcastId)? remote,TResult? Function( String podcastId)? downloaded,}) {final _that = this; +switch (_that) { +case RemoteEpisodes() when remote != null: +return remote(_that.podcastId);case DownloadedEpisodes() when downloaded != null: +return downloaded(_that.podcastId);case _: + return null; + +} +} + +} + +/// @nodoc + + +class RemoteEpisodes implements PodcastEpisodeSource { + const RemoteEpisodes(this.podcastId); + + +@override final String podcastId; + +/// Create a copy of PodcastEpisodeSource +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$RemoteEpisodesCopyWith get copyWith => _$RemoteEpisodesCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteEpisodes&&(identical(other.podcastId, podcastId) || other.podcastId == podcastId)); +} + + +@override +int get hashCode => Object.hash(runtimeType,podcastId); + +@override +String toString() { + return 'PodcastEpisodeSource.remote(podcastId: $podcastId)'; +} + + +} + +/// @nodoc +abstract mixin class $RemoteEpisodesCopyWith<$Res> implements $PodcastEpisodeSourceCopyWith<$Res> { + factory $RemoteEpisodesCopyWith(RemoteEpisodes value, $Res Function(RemoteEpisodes) _then) = _$RemoteEpisodesCopyWithImpl; +@override @useResult +$Res call({ + String podcastId +}); + + + + +} +/// @nodoc +class _$RemoteEpisodesCopyWithImpl<$Res> + implements $RemoteEpisodesCopyWith<$Res> { + _$RemoteEpisodesCopyWithImpl(this._self, this._then); + + final RemoteEpisodes _self; + final $Res Function(RemoteEpisodes) _then; + +/// Create a copy of PodcastEpisodeSource +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? podcastId = null,}) { + return _then(RemoteEpisodes( +null == podcastId ? _self.podcastId : podcastId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class DownloadedEpisodes implements PodcastEpisodeSource { + const DownloadedEpisodes(this.podcastId); + + +@override final String podcastId; + +/// Create a copy of PodcastEpisodeSource +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$DownloadedEpisodesCopyWith get copyWith => _$DownloadedEpisodesCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadedEpisodes&&(identical(other.podcastId, podcastId) || other.podcastId == podcastId)); +} + + +@override +int get hashCode => Object.hash(runtimeType,podcastId); + +@override +String toString() { + return 'PodcastEpisodeSource.downloaded(podcastId: $podcastId)'; +} + + +} + +/// @nodoc +abstract mixin class $DownloadedEpisodesCopyWith<$Res> implements $PodcastEpisodeSourceCopyWith<$Res> { + factory $DownloadedEpisodesCopyWith(DownloadedEpisodes value, $Res Function(DownloadedEpisodes) _then) = _$DownloadedEpisodesCopyWithImpl; +@override @useResult +$Res call({ + String podcastId +}); + + + + +} +/// @nodoc +class _$DownloadedEpisodesCopyWithImpl<$Res> + implements $DownloadedEpisodesCopyWith<$Res> { + _$DownloadedEpisodesCopyWithImpl(this._self, this._then); + + final DownloadedEpisodes _self; + final $Res Function(DownloadedEpisodes) _then; + +/// Create a copy of PodcastEpisodeSource +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? podcastId = null,}) { + return _then(DownloadedEpisodes( +null == podcastId ? _self.podcastId : podcastId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc +mixin _$PodcastEpisodesState { + + Podcast get podcast; List get episodes; int get page; bool get hasNextPage; bool get isLoadingMore; Object? get loadMoreError; +/// Create a copy of PodcastEpisodesState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PodcastEpisodesStateCopyWith get copyWith => _$PodcastEpisodesStateCopyWithImpl(this as PodcastEpisodesState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PodcastEpisodesState&&(identical(other.podcast, podcast) || other.podcast == podcast)&&const DeepCollectionEquality().equals(other.episodes, episodes)&&(identical(other.page, page) || other.page == page)&&(identical(other.hasNextPage, hasNextPage) || other.hasNextPage == hasNextPage)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&const DeepCollectionEquality().equals(other.loadMoreError, loadMoreError)); +} + + +@override +int get hashCode => Object.hash(runtimeType,podcast,const DeepCollectionEquality().hash(episodes),page,hasNextPage,isLoadingMore,const DeepCollectionEquality().hash(loadMoreError)); + +@override +String toString() { + return 'PodcastEpisodesState(podcast: $podcast, episodes: $episodes, page: $page, hasNextPage: $hasNextPage, isLoadingMore: $isLoadingMore, loadMoreError: $loadMoreError)'; +} + + +} + +/// @nodoc +abstract mixin class $PodcastEpisodesStateCopyWith<$Res> { + factory $PodcastEpisodesStateCopyWith(PodcastEpisodesState value, $Res Function(PodcastEpisodesState) _then) = _$PodcastEpisodesStateCopyWithImpl; +@useResult +$Res call({ + Podcast podcast, List episodes, int page, bool hasNextPage, bool isLoadingMore, Object? loadMoreError +}); + + +$PodcastCopyWith<$Res> get podcast; + +} +/// @nodoc +class _$PodcastEpisodesStateCopyWithImpl<$Res> + implements $PodcastEpisodesStateCopyWith<$Res> { + _$PodcastEpisodesStateCopyWithImpl(this._self, this._then); + + final PodcastEpisodesState _self; + final $Res Function(PodcastEpisodesState) _then; + +/// Create a copy of PodcastEpisodesState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? podcast = null,Object? episodes = null,Object? page = null,Object? hasNextPage = null,Object? isLoadingMore = null,Object? loadMoreError = freezed,}) { + return _then(_self.copyWith( +podcast: null == podcast ? _self.podcast : podcast // ignore: cast_nullable_to_non_nullable +as Podcast,episodes: null == episodes ? _self.episodes : episodes // ignore: cast_nullable_to_non_nullable +as List,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable +as int,hasNextPage: null == hasNextPage ? _self.hasNextPage : hasNextPage // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable +as bool,loadMoreError: freezed == loadMoreError ? _self.loadMoreError : loadMoreError , + )); +} +/// Create a copy of PodcastEpisodesState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PodcastCopyWith<$Res> get podcast { + + return $PodcastCopyWith<$Res>(_self.podcast, (value) { + return _then(_self.copyWith(podcast: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [PodcastEpisodesState]. +extension PodcastEpisodesStatePatterns on PodcastEpisodesState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PodcastEpisodesState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PodcastEpisodesState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PodcastEpisodesState value) $default,){ +final _that = this; +switch (_that) { +case _PodcastEpisodesState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PodcastEpisodesState value)? $default,){ +final _that = this; +switch (_that) { +case _PodcastEpisodesState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( Podcast podcast, List episodes, int page, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PodcastEpisodesState() when $default != null: +return $default(_that.podcast,_that.episodes,_that.page,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( Podcast podcast, List episodes, int page, bool hasNextPage, bool isLoadingMore, Object? loadMoreError) $default,) {final _that = this; +switch (_that) { +case _PodcastEpisodesState(): +return $default(_that.podcast,_that.episodes,_that.page,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( Podcast podcast, List episodes, int page, bool hasNextPage, bool isLoadingMore, Object? loadMoreError)? $default,) {final _that = this; +switch (_that) { +case _PodcastEpisodesState() when $default != null: +return $default(_that.podcast,_that.episodes,_that.page,_that.hasNextPage,_that.isLoadingMore,_that.loadMoreError);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PodcastEpisodesState implements PodcastEpisodesState { + const _PodcastEpisodesState({required this.podcast, final List episodes = const [], this.page = 0, this.hasNextPage = false, this.isLoadingMore = false, this.loadMoreError}): _episodes = episodes; + + +@override final Podcast podcast; + final List _episodes; +@override@JsonKey() List get episodes { + if (_episodes is EqualUnmodifiableListView) return _episodes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_episodes); +} + +@override@JsonKey() final int page; +@override@JsonKey() final bool hasNextPage; +@override@JsonKey() final bool isLoadingMore; +@override final Object? loadMoreError; + +/// Create a copy of PodcastEpisodesState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PodcastEpisodesStateCopyWith<_PodcastEpisodesState> get copyWith => __$PodcastEpisodesStateCopyWithImpl<_PodcastEpisodesState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PodcastEpisodesState&&(identical(other.podcast, podcast) || other.podcast == podcast)&&const DeepCollectionEquality().equals(other._episodes, _episodes)&&(identical(other.page, page) || other.page == page)&&(identical(other.hasNextPage, hasNextPage) || other.hasNextPage == hasNextPage)&&(identical(other.isLoadingMore, isLoadingMore) || other.isLoadingMore == isLoadingMore)&&const DeepCollectionEquality().equals(other.loadMoreError, loadMoreError)); +} + + +@override +int get hashCode => Object.hash(runtimeType,podcast,const DeepCollectionEquality().hash(_episodes),page,hasNextPage,isLoadingMore,const DeepCollectionEquality().hash(loadMoreError)); + +@override +String toString() { + return 'PodcastEpisodesState(podcast: $podcast, episodes: $episodes, page: $page, hasNextPage: $hasNextPage, isLoadingMore: $isLoadingMore, loadMoreError: $loadMoreError)'; +} + + +} + +/// @nodoc +abstract mixin class _$PodcastEpisodesStateCopyWith<$Res> implements $PodcastEpisodesStateCopyWith<$Res> { + factory _$PodcastEpisodesStateCopyWith(_PodcastEpisodesState value, $Res Function(_PodcastEpisodesState) _then) = __$PodcastEpisodesStateCopyWithImpl; +@override @useResult +$Res call({ + Podcast podcast, List episodes, int page, bool hasNextPage, bool isLoadingMore, Object? loadMoreError +}); + + +@override $PodcastCopyWith<$Res> get podcast; + +} +/// @nodoc +class __$PodcastEpisodesStateCopyWithImpl<$Res> + implements _$PodcastEpisodesStateCopyWith<$Res> { + __$PodcastEpisodesStateCopyWithImpl(this._self, this._then); + + final _PodcastEpisodesState _self; + final $Res Function(_PodcastEpisodesState) _then; + +/// Create a copy of PodcastEpisodesState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? podcast = null,Object? episodes = null,Object? page = null,Object? hasNextPage = null,Object? isLoadingMore = null,Object? loadMoreError = freezed,}) { + return _then(_PodcastEpisodesState( +podcast: null == podcast ? _self.podcast : podcast // ignore: cast_nullable_to_non_nullable +as Podcast,episodes: null == episodes ? _self._episodes : episodes // ignore: cast_nullable_to_non_nullable +as List,page: null == page ? _self.page : page // ignore: cast_nullable_to_non_nullable +as int,hasNextPage: null == hasNextPage ? _self.hasNextPage : hasNextPage // ignore: cast_nullable_to_non_nullable +as bool,isLoadingMore: null == isLoadingMore ? _self.isLoadingMore : isLoadingMore // ignore: cast_nullable_to_non_nullable +as bool,loadMoreError: freezed == loadMoreError ? _self.loadMoreError : loadMoreError , + )); +} + +/// Create a copy of PodcastEpisodesState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PodcastCopyWith<$Res> get podcast { + + return $PodcastCopyWith<$Res>(_self.podcast, (value) { + return _then(_self.copyWith(podcast: value)); + }); +} +} + +// dart format on diff --git a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart new file mode 100644 index 000000000..a86bdba17 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart @@ -0,0 +1,108 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'episode_list_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(PodcastEpisodesNotifier) +final podcastEpisodesProvider = PodcastEpisodesNotifierFamily._(); + +final class PodcastEpisodesNotifierProvider + extends + $AsyncNotifierProvider { + PodcastEpisodesNotifierProvider._({ + required PodcastEpisodesNotifierFamily super.from, + required PodcastEpisodeSource super.argument, + }) : super( + retry: null, + name: r'podcastEpisodesProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastEpisodesNotifierHash(); + + @override + String toString() { + return r'podcastEpisodesProvider' + '' + '($argument)'; + } + + @$internal + @override + PodcastEpisodesNotifier create() => PodcastEpisodesNotifier(); + + @override + bool operator ==(Object other) { + return other is PodcastEpisodesNotifierProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$podcastEpisodesNotifierHash() => + r'b217a22ee487c619a4b8ba10277a1ebf32c63a17'; + +final class PodcastEpisodesNotifierFamily extends $Family + with + $ClassFamilyOverride< + PodcastEpisodesNotifier, + AsyncValue, + PodcastEpisodesState, + FutureOr, + PodcastEpisodeSource + > { + PodcastEpisodesNotifierFamily._() + : super( + retry: null, + name: r'podcastEpisodesProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + PodcastEpisodesNotifierProvider call(PodcastEpisodeSource source) => + PodcastEpisodesNotifierProvider._(argument: source, from: this); + + @override + String toString() => r'podcastEpisodesProvider'; +} + +abstract class _$PodcastEpisodesNotifier + extends $AsyncNotifier { + late final _$args = ref.$arg as PodcastEpisodeSource; + PodcastEpisodeSource get source => _$args; + + FutureOr build(PodcastEpisodeSource source); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = + this.ref + as $Ref, PodcastEpisodesState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + AsyncValue, + PodcastEpisodesState + >, + AsyncValue, + Object?, + Object? + >; + return element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart new file mode 100644 index 000000000..030a85244 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/ui/podcast_list/podcast_list_viewmodel.dart'; +import 'package:mobile_app_new/podcasts/ui/widgets/podcast_grid_tile.dart'; +import 'package:mobile_app_new/routing/podcasts.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +class PodcastListView extends ConsumerWidget { + const PodcastListView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final podcasts = ref.watch(podcastListProvider); + + return ColoredBox( + color: FccColors.gray80, + child: podcasts.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => ErrorRetry( + message: 'Unable to load podcasts.', + onRetry: () => ref.invalidate(podcastListProvider), + ), + data: (podcasts) => RefreshIndicator( + onRefresh: () async => ref.invalidate(podcastListProvider), + backgroundColor: FccColors.gray90, + color: Colors.white, + child: GridView.count( + crossAxisCount: 2, + childAspectRatio: 1, + children: [ + for (final podcast in podcasts) + PodcastGridTile( + key: ValueKey(podcast.id), + podcast: podcast, + onTap: () => PodcastEpisodeListRoute( + podcastId: podcast.id, + ).push(context), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.dart b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.dart new file mode 100644 index 000000000..388fef712 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.dart @@ -0,0 +1,9 @@ +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/services/api_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'podcast_list_viewmodel.g.dart'; + +@riverpod +Future> podcastList(Ref ref) => + ref.watch(podcastApiServiceProvider).getPodcasts(); diff --git a/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.g.dart b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.g.dart new file mode 100644 index 000000000..9e76a3b65 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.g.dart @@ -0,0 +1,49 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'podcast_list_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(podcastList) +final podcastListProvider = PodcastListProvider._(); + +final class PodcastListProvider + extends + $FunctionalProvider< + AsyncValue>, + List, + FutureOr> + > + with $FutureModifier>, $FutureProvider> { + PodcastListProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'podcastListProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$podcastListHash(); + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + return podcastList(ref); + } +} + +String _$podcastListHash() => r'050470de646ff921b6da31c2026f07ca7a8405df'; diff --git a/mobile-app-new/lib/podcasts/ui/podcasts_shell.dart b/mobile-app-new/lib/podcasts/ui/podcasts_shell.dart new file mode 100644 index 000000000..223c1b0bb --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/podcasts_shell.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/routing/podcasts.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; + +typedef _Tab = ({String title, String path, IconData icon}); + +class PodcastsShell extends StatelessWidget { + const PodcastsShell({super.key, required this.child}); + + final Widget child; + + static const List<_Tab> _tabs = [ + (title: 'Podcasts', path: podcastListPath, icon: Icons.grid_view_rounded), + ( + title: 'Downloads', + path: podcastDownloadsPath, + icon: Icons.arrow_circle_down_sharp, + ), + ]; + + static const _listIndex = 0; + + int _currentIndex(BuildContext context) { + final location = GoRouterState.of(context).uri.path; + final index = _tabs.indexWhere((tab) => tab.path == location); + return index == -1 ? _listIndex : index; + } + + @override + Widget build(BuildContext context) { + final index = _currentIndex(context); + + return Scaffold( + appBar: AppBar(title: Text(_tabs[index].title)), + drawer: const DrawerWidget(), + body: child, + bottomNavigationBar: BottomNavigationBar( + currentIndex: index, + onTap: (i) => context.go(_tabs[i].path), + items: [ + for (final tab in _tabs) + BottomNavigationBarItem( + icon: Icon(tab.icon), + label: tab.title, + tooltip: tab.title, + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/podcasts/ui/widgets/download_button.dart b/mobile-app-new/lib/podcasts/ui/widgets/download_button.dart new file mode 100644 index 000000000..856921479 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/widgets/download_button.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/controllers/downloads_controller.dart'; +import 'package:mobile_app_new/podcasts/controllers/library_controller.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; + +class PodcastDownloadButton extends ConsumerWidget { + const PodcastDownloadButton({ + super.key, + required this.podcast, + required this.episode, + this.size = 33, + }); + + final Podcast podcast; + final Episode episode; + final double size; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final downloadState = ref.watch( + podcastDownloadsProvider.select((state) => state[episode.id]), + ); + final isDownloaded = ref.watch( + podcastLibraryProvider.select( + (state) => + state.value?.episodesByPodcastId[episode.podcastId]?.any( + (downloaded) => downloaded.id == episode.id, + ) ?? + false, + ), + ); + + final isRunning = downloadState is DownloadRunning; + + return IconButton( + onPressed: () => _onPressed(ref, downloadState, isDownloaded), + iconSize: size, + style: ButtonStyle( + shape: WidgetStateProperty.all(const CircleBorder()), + side: WidgetStateProperty.all( + isRunning + ? BorderSide.none + : const BorderSide(color: FccColors.gray80, width: 1), + ), + ), + icon: _icon(downloadState, isDownloaded), + ); + } + + void _onPressed( + WidgetRef ref, + DownloadStatus? downloadState, + bool isDownloaded, + ) => switch (downloadState) { + DownloadRunning() => + ref.read(podcastDownloadsProvider.notifier).cancel(episode.id), + DownloadFailed() => + ref.read(podcastDownloadsProvider.notifier).start(podcast, episode), + null when isDownloaded => + ref + .read(podcastLibraryProvider.notifier) + .remove(episode.podcastId, episode.id), + null => ref.read(podcastDownloadsProvider.notifier).start(podcast, episode), + }; + + Widget _icon(DownloadStatus? downloadState, bool isDownloaded) => + switch (downloadState) { + DownloadRunning(:final progress) => SizedBox.square( + dimension: size, + child: Stack( + alignment: Alignment.center, + children: [ + CircularProgressIndicator( + color: Colors.white, + strokeWidth: 1, + value: progress, + ), + Text( + '${(progress * 100).round()}%', + style: const TextStyle(fontSize: 12), + ), + ], + ), + ), + DownloadFailed() => Icon( + Icons.refresh, + color: FccColors.red15, + semanticLabel: 'Download failed, tap to retry', + ), + null when isDownloaded => Icon( + Icons.download_done, + color: FccColors.gray10, + semanticLabel: 'Downloaded, tap to delete', + ), + null => Icon( + Icons.download, + color: FccColors.gray10, + semanticLabel: 'Download episode', + ), + }; +} diff --git a/mobile-app-new/lib/podcasts/ui/widgets/episode_tile.dart b/mobile-app-new/lib/podcasts/ui/widgets/episode_tile.dart new file mode 100644 index 000000000..b1885b99c --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/widgets/episode_tile.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:html/parser.dart' as html_parser; +import 'package:jiffy/jiffy.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/controllers/player_controller.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/ui/widgets/download_button.dart'; +import 'package:mobile_app_new/routing/podcasts.dart'; + +class PodcastEpisodeTile extends StatelessWidget { + const PodcastEpisodeTile({ + super.key, + required this.podcast, + required this.episode, + }); + + final Podcast podcast; + final Episode episode; + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: FccColors.gray90, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () => PodcastEpisodeRoute( + podcastId: podcast.id, + episodeId: episode.id, + $extra: (podcast, episode), + ).push(context), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _PublishedAt(date: episode.publicationDate), + const SizedBox(height: 6), + Text( + episode.title, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 20, + color: FccColors.gray00, + ), + ), + _Summary(description: episode.description), + Row( + children: [ + _PodcastPlayButton(podcast: podcast, episode: episode), + const SizedBox(width: 12), + PodcastDownloadButton(podcast: podcast, episode: episode), + ], + ), + ], + ), + ), + ), + ), + ); + } +} + +class _PodcastPlayButton extends ConsumerWidget { + const _PodcastPlayButton({required this.podcast, required this.episode}); + + final Podcast podcast; + final Episode episode; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final status = ref.watch( + podcastPlayerProvider.select( + (state) => ( + isPlaying: state.isPlayingEpisode(episode.id), + isBusy: state.isBusyWith(episode.id), + ), + ), + ); + + return IconButton( + onPressed: () => + ref.read(podcastPlayerProvider.notifier).toggle(podcast, episode), + iconSize: 33, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + status.isPlaying ? FccColors.purple90 : FccColors.gray80, + ), + shape: WidgetStateProperty.all(const CircleBorder()), + ), + icon: status.isBusy + ? const SizedBox.square( + dimension: 33, + child: Padding( + padding: EdgeInsets.all(2), + child: CircularProgressIndicator( + color: FccColors.gray00, + strokeWidth: 1.8, + ), + ), + ) + : Icon( + status.isPlaying ? Icons.pause : Icons.play_arrow, + color: FccColors.gray00, + size: 33, + semanticLabel: status.isPlaying + ? 'Pause episode' + : 'Play episode', + ), + ); + } +} + +class _PublishedAt extends StatelessWidget { + const _PublishedAt({required this.date}); + + final DateTime? date; + + @override + Widget build(BuildContext context) { + if (date == null) return const SizedBox.shrink(); + + return Text( + Jiffy.parseFromDateTime(date!).fromNow().toUpperCase(), + style: const TextStyle( + fontSize: 12, + color: FccColors.gray10, + fontWeight: FontWeight.w600, + letterSpacing: 1.1, + ), + ); + } +} + +class _Summary extends StatelessWidget { + const _Summary({required this.description}); + + final String description; + + static String _firstSentence(String html) { + if (html.isEmpty) return ''; + + final text = html_parser.parse(html).body?.text.trim() ?? ''; + final match = RegExp(r'([^.?!]*[.?!])').firstMatch(text); + final sentence = match?.group(0)?.trim() ?? text; + + return sentence.length > 120 + ? '${sentence.substring(0, 120).trimRight()}...' + : sentence; + } + + @override + Widget build(BuildContext context) { + final sentence = _firstSentence(description); + if (sentence.isEmpty) return const SizedBox(height: 14); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 6), + Text( + sentence, + style: const TextStyle(color: FccColors.gray10, fontSize: 16), + ), + const SizedBox(height: 14), + ], + ); + } +} diff --git a/mobile-app-new/lib/podcasts/ui/widgets/podcast_grid_tile.dart b/mobile-app-new/lib/podcasts/ui/widgets/podcast_grid_tile.dart new file mode 100644 index 000000000..4c13fead2 --- /dev/null +++ b/mobile-app-new/lib/podcasts/ui/widgets/podcast_grid_tile.dart @@ -0,0 +1,77 @@ +import 'dart:io'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; + +class PodcastGridTile extends StatelessWidget { + const PodcastGridTile({ + super.key, + required this.podcast, + required this.onTap, + this.artwork, + }); + + final Podcast podcast; + final VoidCallback onTap; + + final File? artwork; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(4), + child: Stack( + fit: StackFit.expand, + children: [ + ColoredBox(color: FccColors.gray90, child: _artwork()), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: DecoratedBox( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.75), + spreadRadius: 1.5, + blurRadius: 0.1, + ), + ], + ), + child: Text( + podcast.title, + maxLines: 2, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 16, + height: 1.2, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ], + ), + ), + ); + } + + Widget _artwork() { + if (artwork case final file?) return Image.file(file, fit: BoxFit.cover); + + if (podcast.imageLink.isEmpty) { + return const Icon(Icons.podcasts, color: FccColors.gray45, size: 48); + } + + return CachedNetworkImage( + imageUrl: podcast.imageLink, + fit: BoxFit.cover, + errorWidget: (_, _, _) => + const Icon(Icons.podcasts, color: FccColors.gray45, size: 48), + ); + } +} diff --git a/mobile-app-new/lib/routing/podcasts.dart b/mobile-app-new/lib/routing/podcasts.dart new file mode 100644 index 000000000..f2719a75d --- /dev/null +++ b/mobile-app-new/lib/routing/podcasts.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; +import 'package:mobile_app_new/podcasts/ui/downloads/downloads_view.dart'; +import 'package:mobile_app_new/podcasts/ui/episode/episode_view.dart'; +import 'package:mobile_app_new/podcasts/ui/episode_list/episode_list_view.dart'; +import 'package:mobile_app_new/podcasts/ui/episode_list/episode_list_viewmodel.dart'; +import 'package:mobile_app_new/podcasts/ui/podcast_list/podcast_list_view.dart'; +import 'package:mobile_app_new/podcasts/ui/podcasts_shell.dart'; + +part 'podcasts.g.dart'; + +const podcastListPath = '/podcasts'; +const podcastDownloadsPath = '/podcasts/downloads'; +const podcastEpisodeListPath = '/podcasts/:podcastId'; +const podcastEpisodePath = '/podcasts/:podcastId/:episodeId'; + +@TypedShellRoute( + routes: [ + TypedGoRoute(path: podcastListPath), + TypedGoRoute(path: podcastDownloadsPath), + ], +) +class PodcastsShellRoute extends ShellRouteData { + const PodcastsShellRoute(); + + @override + Widget builder(BuildContext context, GoRouterState state, Widget navigator) { + return PodcastsShell(child: navigator); + } +} + +class PodcastListRoute extends GoRouteData with $PodcastListRoute { + const PodcastListRoute(); + + @override + Page buildPage(BuildContext context, GoRouterState state) => + const NoTransitionPage(child: PodcastListView()); +} + +class PodcastDownloadsRoute extends GoRouteData with $PodcastDownloadsRoute { + const PodcastDownloadsRoute(); + + @override + Page buildPage(BuildContext context, GoRouterState state) => + const NoTransitionPage(child: PodcastDownloadsView()); +} + +@TypedGoRoute(path: podcastEpisodeListPath) +class PodcastEpisodeListRoute extends GoRouteData + with $PodcastEpisodeListRoute { + const PodcastEpisodeListRoute({ + required this.podcastId, + this.downloaded = false, + }); + + final String podcastId; + final bool downloaded; + + @override + Widget build(BuildContext context, GoRouterState state) => + PodcastEpisodeListView( + source: downloaded + ? PodcastEpisodeSource.downloaded(podcastId) + : PodcastEpisodeSource.remote(podcastId), + ); +} + +@TypedGoRoute(path: podcastEpisodePath) +class PodcastEpisodeRoute extends GoRouteData with $PodcastEpisodeRoute { + const PodcastEpisodeRoute({ + required this.podcastId, + required this.episodeId, + required this.$extra, + }); + + final String podcastId; + final String episodeId; + + // NOTE: Required rather than nullable. There is a single-episode endpoint + // that would let this route resolve from its path alone, but nothing + // deep-links to podcasts today and notification deep-linking is a + // post-migration feature, so the fallback is not built yet. Safe only while + // that stays true AND MaterialApp.router has no restorationScopeId. + final (Podcast, Episode) $extra; + + @override + Widget build(BuildContext context, GoRouterState state) => + PodcastEpisodeView(podcast: $extra.$1, episode: $extra.$2); +} diff --git a/mobile-app-new/lib/routing/podcasts.g.dart b/mobile-app-new/lib/routing/podcasts.g.dart new file mode 100644 index 000000000..de65cffd9 --- /dev/null +++ b/mobile-app-new/lib/routing/podcasts.g.dart @@ -0,0 +1,176 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'podcasts.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $podcastsShellRoute, + $podcastEpisodeListRoute, + $podcastEpisodeRoute, +]; + +RouteBase get $podcastsShellRoute => ShellRouteData.$route( + factory: $PodcastsShellRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: '/podcasts', + hasOverriddenOnExit: false, + factory: $PodcastListRoute._fromState, + ), + GoRouteData.$route( + path: '/podcasts/downloads', + hasOverriddenOnExit: false, + factory: $PodcastDownloadsRoute._fromState, + ), + ], +); + +extension $PodcastsShellRouteExtension on PodcastsShellRoute { + static PodcastsShellRoute _fromState(GoRouterState state) => + const PodcastsShellRoute(); +} + +mixin $PodcastListRoute on GoRouteData { + static PodcastListRoute _fromState(GoRouterState state) => + const PodcastListRoute(); + + @override + String get location => GoRouteData.$location('/podcasts'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +mixin $PodcastDownloadsRoute on GoRouteData { + static PodcastDownloadsRoute _fromState(GoRouterState state) => + const PodcastDownloadsRoute(); + + @override + String get location => GoRouteData.$location('/podcasts/downloads'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $podcastEpisodeListRoute => GoRouteData.$route( + path: '/podcasts/:podcastId', + hasOverriddenOnExit: false, + factory: $PodcastEpisodeListRoute._fromState, +); + +mixin $PodcastEpisodeListRoute on GoRouteData { + static PodcastEpisodeListRoute _fromState(GoRouterState state) => + PodcastEpisodeListRoute( + podcastId: state.pathParameters['podcastId']!, + downloaded: + _$convertMapValue( + 'downloaded', + state.uri.queryParameters, + _$boolConverter, + ) ?? + false, + ); + + PodcastEpisodeListRoute get _self => this as PodcastEpisodeListRoute; + + @override + String get location => GoRouteData.$location( + '/podcasts/${Uri.encodeComponent(_self.podcastId)}', + queryParams: { + if (_self.downloaded != false) 'downloaded': _self.downloaded.toString(), + }, + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +T? _$convertMapValue( + String key, + Map map, + T? Function(String) converter, +) { + final value = map[key]; + return value == null ? null : converter(value); +} + +bool _$boolConverter(String value) { + switch (value) { + case 'true': + return true; + case 'false': + return false; + default: + throw UnsupportedError('Cannot convert "$value" into a bool.'); + } +} + +RouteBase get $podcastEpisodeRoute => GoRouteData.$route( + path: '/podcasts/:podcastId/:episodeId', + hasOverriddenOnExit: false, + factory: $PodcastEpisodeRoute._fromState, +); + +mixin $PodcastEpisodeRoute on GoRouteData { + static PodcastEpisodeRoute _fromState(GoRouterState state) => + PodcastEpisodeRoute( + podcastId: state.pathParameters['podcastId']!, + episodeId: state.pathParameters['episodeId']!, + $extra: state.extra as (Podcast, Episode), + ); + + PodcastEpisodeRoute get _self => this as PodcastEpisodeRoute; + + @override + String get location => GoRouteData.$location( + '/podcasts/${Uri.encodeComponent(_self.podcastId)}/${Uri.encodeComponent(_self.episodeId)}', + ); + + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + + @override + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: _self.$extra); + + @override + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); +} diff --git a/mobile-app-new/lib/routing/router.dart b/mobile-app-new/lib/routing/router.dart index 28dc9498a..7a716288e 100644 --- a/mobile-app-new/lib/routing/router.dart +++ b/mobile-app-new/lib/routing/router.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/routing/code_radio.dart' as code_radio_routes; import 'package:mobile_app_new/routing/learn.dart' as learn_routes; import 'package:mobile_app_new/routing/news.dart' as news_routes; +import 'package:mobile_app_new/routing/podcasts.dart' as podcasts_routes; final _initialLocation = const learn_routes.LearnLandingRoute().location; @@ -14,6 +15,7 @@ final router = GoRouter( ...learn_routes.$appRoutes, ...news_routes.$appRoutes, ...code_radio_routes.$appRoutes, + ...podcasts_routes.$appRoutes, ], errorBuilder: (context, state) => Scaffold( appBar: AppBar(title: const Text('Page Not Found')), diff --git a/mobile-app-new/lib/services/audio_service.dart b/mobile-app-new/lib/services/audio_service.dart index 9093e196e..909e4f76d 100644 --- a/mobile-app-new/lib/services/audio_service.dart +++ b/mobile-app-new/lib/services/audio_service.dart @@ -5,10 +5,18 @@ import 'package:audio_service/audio_service.dart'; import 'package:just_audio/just_audio.dart'; import 'package:mobile_app_new/code_radio/models/code_radio_model.dart'; import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/podcasts/models/episode_model.dart'; +import 'package:mobile_app_new/podcasts/models/podcast_model.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'audio_service.g.dart'; +typedef _MediaControls = ({ + List controls, + Set systemActions, + List compactIndices, +}); + sealed class AudioTypeConfig { const AudioTypeConfig(); } @@ -17,6 +25,12 @@ class CodeRadioAudioConfig extends AudioTypeConfig { const CodeRadioAudioConfig(); } +class PodcastAudioConfig extends AudioTypeConfig { + const PodcastAudioConfig(this.episodeId); + + final String episodeId; +} + // NOTE: The handler is created before runApp and injected, so reading // this without that override is a wiring mistake rather than a lazy init. @Riverpod(keepAlive: true) @@ -41,6 +55,10 @@ class AudioPlayerHandler extends BaseAudioHandler { _notifyAudioHandlerAboutPlaybackEvents(); } + static const _skipForward = Duration(seconds: 30); + static const _skipBackward = Duration(seconds: 10); + static const _defaultSpeed = 1.0; + final AudioPlayer _audioPlayer = AudioPlayer(); AudioTypeConfig? _audioConfig; @@ -53,14 +71,21 @@ class AudioPlayerHandler extends BaseAudioHandler { // only way back to the live edge. bool _resumeNeedsLiveEdge = false; - AudioTypeConfig? get audioConfig => _audioConfig; - String? get codeRadioSongId => _audioConfig is CodeRadioAudioConfig ? mediaItem.value?.id : null; bool get isPlayingCodeRadio => _audioPlayer.playing && _audioConfig is CodeRadioAudioConfig; + String? get episodeId => switch (_audioConfig) { + PodcastAudioConfig(:final episodeId) => episodeId, + _ => null, + }; + + Stream get positionStream => _audioPlayer.positionStream; + + Duration? get duration => _audioPlayer.duration; + @override Future play() async { if (_resumeNeedsLiveEdge) { @@ -71,7 +96,7 @@ class AudioPlayerHandler extends BaseAudioHandler { } } - await _audioPlayer.play(); + _audioPlayer.play(); } @override @@ -83,34 +108,76 @@ class AudioPlayerHandler extends BaseAudioHandler { @override Future stop() async { - await _audioPlayer.stop(); _audioConfig = null; _codeRadioUrl = null; _resumeNeedsLiveEdge = false; + + await _audioPlayer.stop(); + + playbackState.add( + playbackState.value.copyWith( + processingState: AudioProcessingState.idle, + playing: false, + controls: const [], + systemActions: const {}, + androidCompactActionIndices: const [], + updatePosition: Duration.zero, + bufferedPosition: Duration.zero, + speed: _defaultSpeed, + ), + ); + + mediaItem.add(null); + queue.add(const []); + return super.stop(); } @override Future seek(Duration position) => _audioPlayer.seek(position); + @override + Future fastForward() => _seekBy(_skipForward); + + @override + Future rewind() => _seekBy(-_skipBackward); + + @override + Future setSpeed(double speed) => _audioPlayer.setSpeed(speed); + @override Future onTaskRemoved() async { await stop(); return super.onTaskRemoved(); } + Future _seekBy(Duration offset) async { + final target = _audioPlayer.position + offset; + final end = _audioPlayer.duration; + + if (target < Duration.zero) return _audioPlayer.seek(Duration.zero); + if (end != null && target > end) return _audioPlayer.seek(end); + + return _audioPlayer.seek(target); + } + Future loadCodeRadio(CodeRadio radio) async { + final previous = _audioConfig; + try { final song = _toMediaItem(radio.nowPlaying.song); final url = Uri.parse(radio.station.listenUrl); - await _audioPlayer.setAudioSource(AudioSource.uri(url, tag: song)); - _codeRadioUrl = url; _resumeNeedsLiveEdge = false; _audioConfig = const CodeRadioAudioConfig(); + + await _audioPlayer.setSpeed(_defaultSpeed); + await _audioPlayer.setAudioSource(AudioSource.uri(url, tag: song)); + _publish(song); } catch (e) { + _audioConfig = previous; log('loadCodeRadio: Cannot play audio: $e'); } } @@ -130,6 +197,42 @@ class AudioPlayerHandler extends BaseAudioHandler { } } + Future loadEpisode( + Episode episode, + Podcast podcast, { + required Uri source, + Uri? artUri, + Duration? startAt, + }) async { + final item = MediaItem( + id: episode.id, + title: episode.title, + album: podcast.title, + duration: episode.duration, + artUri: artUri, + ); + + _codeRadioUrl = null; + _resumeNeedsLiveEdge = false; + _audioConfig = PodcastAudioConfig(episode.id); + + try { + await _audioPlayer.setSpeed(_defaultSpeed); + await _audioPlayer.setAudioSource(AudioSource.uri(source, tag: item)); + + if (startAt != null && startAt > Duration.zero) { + await _audioPlayer.seek(startAt); + } + + _publish(item); + return true; + } catch (e) { + log('loadEpisode: Cannot play audio: $e'); + await stop(); + return false; + } + } + MediaItem _toMediaItem(Song song) => MediaItem( id: song.id, title: song.title, @@ -143,18 +246,47 @@ class AudioPlayerHandler extends BaseAudioHandler { mediaItem.add(song); } + _MediaControls _controlsFor(bool playing) { + final playPause = playing ? MediaControl.pause : MediaControl.play; + + return switch (_audioConfig) { + PodcastAudioConfig() => ( + controls: [ + MediaControl.rewind, + playPause, + MediaControl.fastForward, + MediaControl.stop, + ], + systemActions: const {MediaAction.seek}, + compactIndices: const [0, 1, 2], + ), + CodeRadioAudioConfig() || null => ( + controls: [playPause, MediaControl.stop], + systemActions: const {}, + compactIndices: const [0, 1], + ), + }; + } + void _notifyAudioHandlerAboutPlaybackEvents() { _audioPlayer.playbackEventStream.listen( (PlaybackEvent event) { + if (_audioConfig == null) return; + + if (_audioConfig is PodcastAudioConfig && + _audioPlayer.processingState == ProcessingState.completed) { + unawaited(Future.microtask(stop)); + return; + } + final playing = _audioPlayer.playing; + final mediaControls = _controlsFor(playing); + playbackState.add( playbackState.value.copyWith( - controls: [ - if (playing) MediaControl.pause else MediaControl.play, - MediaControl.stop, - ], - systemActions: const {}, - androidCompactActionIndices: const [0, 1], + controls: mediaControls.controls, + systemActions: mediaControls.systemActions, + androidCompactActionIndices: mediaControls.compactIndices, processingState: const { ProcessingState.idle: AudioProcessingState.idle, ProcessingState.loading: AudioProcessingState.loading, diff --git a/mobile-app-new/lib/services/notification_service.dart b/mobile-app-new/lib/services/notification_service.dart new file mode 100644 index 000000000..848a99242 --- /dev/null +++ b/mobile-app-new/lib/services/notification_service.dart @@ -0,0 +1,85 @@ +import 'dart:developer'; +import 'dart:io'; +import 'dart:math' show Random, pow; + +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'notification_service.g.dart'; + +@Riverpod(keepAlive: true) +NotificationService notificationService(Ref ref) => NotificationService(); + +class NotificationService { + static const _channelId = 'fcc-notif-channel'; + static const _channelName = 'podcast-episodes'; + + final FlutterLocalNotificationsPlugin _plugin = + FlutterLocalNotificationsPlugin(); + final Random _random = Random(); + + Future? _initialisation; + + Future _ensureInitialised() => _initialisation ??= _plugin.initialize( + settings: const InitializationSettings( + android: AndroidInitializationSettings('notification_icon'), + iOS: DarwinInitializationSettings(), + ), + ); + + Future requestPermission() async { + try { + await _ensureInitialised(); + + if (Platform.isAndroid) { + return await _plugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin + >() + ?.requestNotificationsPermission() ?? + false; + } + + if (Platform.isIOS) { + return await _plugin + .resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin + >() + ?.requestPermissions(alert: true, badge: true, sound: true) ?? + false; + } + } catch (e) { + log('Notifications: permission request failed ($e)'); + return false; + } + + return true; + } + + Future showNotification(String title, String body) async { + try { + await _ensureInitialised(); + await _show(title, body); + } catch (e) { + log('Notifications: could not post "$title" ($e)'); + } + } + + Future _show(String title, String body) => _plugin.show( + id: _random.nextInt(pow(2, 31).toInt() - 1), + title: title, + body: body, + notificationDetails: const NotificationDetails( + android: AndroidNotificationDetails( + _channelId, + _channelName, + channelDescription: 'Notifications about podcast episode downloads', + priority: Priority.high, + importance: Importance.max, + color: FccColors.gray90, + ), + iOS: DarwinNotificationDetails(threadIdentifier: 'fcc-ios-notif-channel'), + ), + ); +} diff --git a/mobile-app-new/lib/services/notification_service.g.dart b/mobile-app-new/lib/services/notification_service.g.dart new file mode 100644 index 000000000..8f0846530 --- /dev/null +++ b/mobile-app-new/lib/services/notification_service.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notification_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(notificationService) +final notificationServiceProvider = NotificationServiceProvider._(); + +final class NotificationServiceProvider + extends + $FunctionalProvider< + NotificationService, + NotificationService, + NotificationService + > + with $Provider { + NotificationServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'notificationServiceProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$notificationServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + NotificationService create(Ref ref) { + return notificationService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(NotificationService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$notificationServiceHash() => + r'585c1e42ea844e71a2b76b80b165adfe2c5c8529'; diff --git a/mobile-app-new/lib/widgets/drawer/drawer.dart b/mobile-app-new/lib/widgets/drawer/drawer.dart index 10c391b7a..d3b681cdf 100644 --- a/mobile-app-new/lib/widgets/drawer/drawer.dart +++ b/mobile-app-new/lib/widgets/drawer/drawer.dart @@ -5,6 +5,7 @@ import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/routing/code_radio.dart'; import 'package:mobile_app_new/routing/learn.dart'; import 'package:mobile_app_new/routing/news.dart'; +import 'package:mobile_app_new/routing/podcasts.dart'; import 'package:mobile_app_new/widgets/drawer/drawer_tile.dart'; class DrawerWidget extends StatelessWidget { @@ -26,6 +27,11 @@ class DrawerWidget extends StatelessWidget { icon: Icons.forum_outlined, route: () => const NewsFeedRoute().go(context), ), + DrawerTile( + component: 'Podcast', + icon: Icons.podcasts, + route: () => const PodcastListRoute().go(context), + ), if (!Platform.isIOS) DrawerTile( component: 'Code Radio', diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 563136a3e..affc3cdf7 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -337,6 +337,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: a48d5da28e89bd02196e80d81ed8d7954923d00a0f4a68cc20b575038f023383 + url: "https://pub.dev" + source: hosted + version: "0.7.15" dio: dependency: "direct main" description: @@ -459,6 +467,46 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "1447ba911c60f2ba3f25dae1af151ec187162566b0f57e37771bf0b400f013ad" + url: "https://pub.dev" + source: hosted + version: "22.3.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "9ca97e63776f29ab1b955725c09999fc2c150523269db150c39274f2a43c5a8b" + url: "https://pub.dev" + source: hosted + version: "8.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "43c3761d916c9bd3d5c7ebbc44d82f4990329840c0c5d62ad5260cc1b5d399bd" + url: "https://pub.dev" + source: hosted + version: "12.2.0" + flutter_local_notifications_web: + dependency: transitive + description: + name: flutter_local_notifications_web + sha256: "516afaf97a2d1e67a036c6617321b00d205d72f7a67b6eccf936cd565f985878" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: "6f43bdd03b171b7a90f22647506fea33e2bb12294b7c7c7a3d690e960a382945" + url: "https://pub.dev" + source: hosted + version: "3.1.1" flutter_riverpod: dependency: "direct main" description: @@ -959,6 +1007,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" phone_ide: dependency: "direct main" description: @@ -1120,7 +1176,7 @@ packages: source: hosted version: "7.2.0" shared_preferences: - dependency: transitive + dependency: "direct main" description: name: shared_preferences sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf @@ -1380,6 +1436,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.17" + timezone: + dependency: transitive + description: + name: timezone + sha256: "981d1020d6ef8fe1e7b3de5054e5b25579ae7c403d7734adc508ffc47668e9cb" + url: "https://pub.dev" + source: hosted + version: "0.11.1" typed_data: dependency: transitive description: @@ -1572,6 +1636,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + url: "https://pub.dev" + source: hosted + version: "7.0.1" yaml: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 6351d7745..d84684b27 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -60,6 +60,8 @@ dependencies: audio_service: 0.18.19 just_audio: 0.10.6 web_socket_channel: 3.0.3 + shared_preferences: 2.5.5 + flutter_local_notifications: 22.3.0 dev_dependencies: flutter_driver: From 2ee855fddb67208b1528b9c8f8b8dad2673ebbac Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 23 Sep 2026 22:06:43 +0530 Subject: [PATCH 31/52] fix: code radio handle failed connections --- .../repositories/websocket_repository.dart | 18 +++++++++++++++--- .../services/code_radio_service.dart | 1 + .../code_radio/ui/player/player_viewmodel.dart | 12 ++++++++---- .../ui/player/player_viewmodel.g.dart | 6 +++--- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart b/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart index 7123148c6..2b3453a03 100644 --- a/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart +++ b/mobile-app-new/lib/code_radio/repositories/websocket_repository.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:developer'; @@ -11,6 +12,8 @@ const _url = const _station = 'station:coderadio'; +const _connectTimeout = Duration(seconds: 8); + @Riverpod(keepAlive: true) CodeRadioWebsocketRepository codeRadioWebsocketRepository(Ref ref) { final repository = CodeRadioWebsocketRepository(); @@ -22,11 +25,17 @@ class CodeRadioWebsocketRepository { WebSocketChannel? _channel; Stream connect() async* { - await _channel?.sink.close(); + _discard(_channel); final channel = _channel = WebSocketChannel.connect(Uri.parse(_url)); - await channel.ready; + try { + await channel.ready.timeout(_connectTimeout); + } catch (_) { + _discard(channel); + if (identical(_channel, channel)) _channel = null; + rethrow; + } channel.sink.add( jsonEncode({ @@ -37,8 +46,11 @@ class CodeRadioWebsocketRepository { yield* channel.stream.cast(); } + void _discard(WebSocketChannel? channel) => channel?.sink.close().ignore(); + void dispose() { log('Disposing CodeRadioWebsocketRepository'); - _channel?.sink.close(); + _discard(_channel); + _channel = null; } } diff --git a/mobile-app-new/lib/code_radio/services/code_radio_service.dart b/mobile-app-new/lib/code_radio/services/code_radio_service.dart index 84ff057a6..12a56e577 100644 --- a/mobile-app-new/lib/code_radio/services/code_radio_service.dart +++ b/mobile-app-new/lib/code_radio/services/code_radio_service.dart @@ -100,5 +100,6 @@ class CodeRadioService { void _dropStation() { _station?.cancel(); _station = null; + _latest = null; } } diff --git a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart index 717e86076..c749d0778 100644 --- a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart +++ b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.dart @@ -5,7 +5,9 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'player_viewmodel.g.dart'; -@riverpod +Duration? _noRetry(int retryCount, Object error) => null; + +@Riverpod(retry: _noRetry) Stream codeRadioNowPlaying(Ref ref) { final service = ref.watch(codeRadioServiceProvider); @@ -48,10 +50,12 @@ class CodeRadioPlayerNotifier extends _$CodeRadioPlayerNotifier { } Future _load(CodeRadio radio) async { - if (_handler.codeRadioSongId != null) return; + final handler = _handler; + + if (handler.codeRadioSongId != null) return; - await _handler.loadCodeRadio(radio); - await _handler.play(); + await handler.loadCodeRadio(radio); + await handler.play(); } Future toggle() => state ? _handler.pause() : _handler.play(); diff --git a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart index 885246b5c..078d80fcb 100644 --- a/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart +++ b/mobile-app-new/lib/code_radio/ui/player/player_viewmodel.g.dart @@ -20,7 +20,7 @@ final class CodeRadioNowPlayingProvider : super( from: null, argument: null, - retry: null, + retry: _noRetry, name: r'codeRadioNowPlayingProvider', isAutoDispose: true, dependencies: null, @@ -42,7 +42,7 @@ final class CodeRadioNowPlayingProvider } String _$codeRadioNowPlayingHash() => - r'642ff517d7359e73249732ea7ccbedfa1c04e48f'; + r'4e8aa95e62f6b2d4dd82363283a5134708abf4b2'; @ProviderFor(codeRadioElapsed) final codeRadioElapsedProvider = CodeRadioElapsedProvider._(); @@ -110,7 +110,7 @@ final class CodeRadioPlayerNotifierProvider } String _$codeRadioPlayerNotifierHash() => - r'763710fdaf1ee286a50a7d40ccad330245da1e2a'; + r'41ace83c0a8ae9b84787322877e033e2a8ef8545'; abstract class _$CodeRadioPlayerNotifier extends $Notifier { bool build(); From 23ba0f953a2429baa11a77a806b782277e448dc6 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 23 Sep 2026 22:07:05 +0530 Subject: [PATCH 32/52] chore: move news constants to a single file --- .../radio_articles.dart => constants.dart} | 0 .../lib/news/controllers/feed_controller.dart | 18 +++++++++++------- .../news/controllers/feed_controller.g.dart | 2 +- .../lib/news/services/search_service.dart | 2 +- .../news/ui/author_feed/author_feed_view.dart | 1 - 5 files changed, 13 insertions(+), 10 deletions(-) rename mobile-app-new/lib/news/{constants/radio_articles.dart => constants.dart} (100%) diff --git a/mobile-app-new/lib/news/constants/radio_articles.dart b/mobile-app-new/lib/news/constants.dart similarity index 100% rename from mobile-app-new/lib/news/constants/radio_articles.dart rename to mobile-app-new/lib/news/constants.dart diff --git a/mobile-app-new/lib/news/controllers/feed_controller.dart b/mobile-app-new/lib/news/controllers/feed_controller.dart index ad7a8df28..ce2d58a85 100644 --- a/mobile-app-new/lib/news/controllers/feed_controller.dart +++ b/mobile-app-new/lib/news/controllers/feed_controller.dart @@ -39,16 +39,20 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { ); } - Future _fetch(NewsApiService service, String cursor) => - switch (source) { - AllPosts() => service.getAllPosts(afterCursor: cursor), - TagPosts(:final slug) => service.getPostsByTag(slug, afterCursor: cursor), - AuthorPosts(:final id) => service.getPostsByAuthor(id, afterCursor: cursor), - }; + Future _fetch( + NewsApiService service, + String cursor, + ) => switch (source) { + AllPosts() => service.getAllPosts(afterCursor: cursor), + TagPosts(:final slug) => service.getPostsByTag(slug, afterCursor: cursor), + AuthorPosts(:final id) => service.getPostsByAuthor(id, afterCursor: cursor), + }; Future fetchNextPage({bool isRetry = false}) async { final current = state.value; - if (current == null || !current.hasNextPage || current.isLoadingMore) return; + if (current == null || !current.hasNextPage || current.isLoadingMore) { + return; + } // A failed append waits for an explicit retry instead of refiring on scroll. if (current.loadMoreError != null && !isRetry) return; diff --git a/mobile-app-new/lib/news/controllers/feed_controller.g.dart b/mobile-app-new/lib/news/controllers/feed_controller.g.dart index 22ea06027..f7011def0 100644 --- a/mobile-app-new/lib/news/controllers/feed_controller.g.dart +++ b/mobile-app-new/lib/news/controllers/feed_controller.g.dart @@ -50,7 +50,7 @@ final class NewsFeedNotifierProvider } } -String _$newsFeedNotifierHash() => r'7365cd1e53036b584af9c460c278883f8ad28e85'; +String _$newsFeedNotifierHash() => r'42bcd116de8475bd49d6e578fa95ccfe1d2f9de9'; final class NewsFeedNotifierFamily extends $Family with diff --git a/mobile-app-new/lib/news/services/search_service.dart b/mobile-app-new/lib/news/services/search_service.dart index 538a9cba4..ddcc58109 100644 --- a/mobile-app-new/lib/news/services/search_service.dart +++ b/mobile-app-new/lib/news/services/search_service.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:algolia_helper_flutter/algolia_helper_flutter.dart'; -import 'package:mobile_app_new/news/constants/radio_articles.dart'; +import 'package:mobile_app_new/news/constants.dart'; import 'package:mobile_app_new/news/models/search_post_model.dart'; import 'package:mobile_app_new/news/repositories/search_repository.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart b/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart index ec0a71399..c5f44df29 100644 --- a/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart +++ b/mobile-app-new/lib/news/ui/author_feed/author_feed_view.dart @@ -44,7 +44,6 @@ class NewsAuthorFeedView extends ConsumerWidget { ); } - class _AuthorDetails extends StatelessWidget { const _AuthorDetails({required this.author}); From f26953a014eb4bac7bd205d5ea665e0dca6adaff Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 23 Sep 2026 22:11:26 +0530 Subject: [PATCH 33/52] choe: format cde --- mobile-app-new/lib/fcc_theme.dart | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mobile-app-new/lib/fcc_theme.dart b/mobile-app-new/lib/fcc_theme.dart index 3a9232d97..43562721a 100644 --- a/mobile-app-new/lib/fcc_theme.dart +++ b/mobile-app-new/lib/fcc_theme.dart @@ -100,9 +100,11 @@ class FccTheme { textButtonTheme: TextButtonThemeData( style: ButtonStyle( backgroundColor: WidgetStateProperty.resolveWith( - (states) => const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1)), - foregroundColor: - WidgetStateProperty.resolveWith((states) => Colors.white), + (states) => const Color.fromRGBO(0x3b, 0x3b, 0x4f, 1), + ), + foregroundColor: WidgetStateProperty.resolveWith( + (states) => Colors.white, + ), overlayColor: WidgetStateProperty.resolveWith( (states) => const Color(0x4DFFFFFF), ), @@ -117,8 +119,8 @@ class FccTheme { ), primaryColorDark: const Color(0xFF0a0a23), primaryIconTheme: ThemeData.dark().primaryIconTheme.copyWith( - color: Colors.orange, - ), + color: Colors.orange, + ), textSelectionTheme: const TextSelectionThemeData( cursorColor: Color.fromRGBO(66, 133, 244, 1.0), selectionHandleColor: Color.fromARGB(255, 255, 255, 255), From 26b1711bb2c8815c69fd9e9229344f753956c818 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 25 Sep 2026 18:15:48 +0530 Subject: [PATCH 34/52] chore: convert Dio into a provider service --- mobile-app-new/lib/main.dart | 2 - .../podcasts/repositories/api_repository.dart | 7 ++- .../repositories/api_repository.g.dart | 2 +- .../repositories/download_repository.dart | 6 ++- .../repositories/download_repository.g.dart | 2 +- mobile-app-new/lib/services/dio_service.dart | 22 ++++---- .../lib/services/dio_service.g.dart | 50 +++++++++++++++++++ 7 files changed, 70 insertions(+), 21 deletions(-) create mode 100644 mobile-app-new/lib/services/dio_service.g.dart diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index cb06c6520..028e2dd87 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -7,12 +7,10 @@ import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/podcasts/services/library_service.dart'; import 'package:mobile_app_new/routing/router.dart'; import 'package:mobile_app_new/services/audio_service.dart'; -import 'package:mobile_app_new/services/dio_service.dart'; import 'package:mobile_app_new/services/notification_service.dart'; Future main() async { await dotenv.load(); - await DioService().init(); final audioHandler = await initAudioService(); diff --git a/mobile-app-new/lib/podcasts/repositories/api_repository.dart b/mobile-app-new/lib/podcasts/repositories/api_repository.dart index 862193886..460f081f9 100644 --- a/mobile-app-new/lib/podcasts/repositories/api_repository.dart +++ b/mobile-app-new/lib/podcasts/repositories/api_repository.dart @@ -14,10 +14,13 @@ const _prodUrl = 'https://api.mobile.freecodecamp.org'; const _devUrl = 'https://api.mobile.freecodecamp.dev'; @riverpod -PodcastApiRepository podcastApiRepository(Ref ref) => PodcastApiRepository(); +PodcastApiRepository podcastApiRepository(Ref ref) => + PodcastApiRepository(ref.watch(dioProvider)); class PodcastApiRepository { - final Dio _dio = DioService.dio; + PodcastApiRepository(this._dio); + + final Dio _dio; String get _baseUrl => dotenv.getBool('DEVELOPMENT_MODE', fallback: false) ? _devUrl : _prodUrl; diff --git a/mobile-app-new/lib/podcasts/repositories/api_repository.g.dart b/mobile-app-new/lib/podcasts/repositories/api_repository.g.dart index 1faa71807..396a41d2e 100644 --- a/mobile-app-new/lib/podcasts/repositories/api_repository.g.dart +++ b/mobile-app-new/lib/podcasts/repositories/api_repository.g.dart @@ -55,4 +55,4 @@ final class PodcastApiRepositoryProvider } String _$podcastApiRepositoryHash() => - r'7ccb4cf53f8bcdf719d87a236334120d5405afa9'; + r'51754e685b2b3785c13cf95ce173845a660ff6bc'; diff --git a/mobile-app-new/lib/podcasts/repositories/download_repository.dart b/mobile-app-new/lib/podcasts/repositories/download_repository.dart index 66579d630..1c0ba7335 100644 --- a/mobile-app-new/lib/podcasts/repositories/download_repository.dart +++ b/mobile-app-new/lib/podcasts/repositories/download_repository.dart @@ -23,10 +23,12 @@ const _artworkDirName = 'podcast'; @riverpod PodcastDownloadRepository podcastDownloadRepository(Ref ref) => - PodcastDownloadRepository(); + PodcastDownloadRepository(ref.watch(dioProvider)); class PodcastDownloadRepository { - final Dio _dio = DioService.dio; + PodcastDownloadRepository(this._dio); + + final Dio _dio; Directory? _documents; diff --git a/mobile-app-new/lib/podcasts/repositories/download_repository.g.dart b/mobile-app-new/lib/podcasts/repositories/download_repository.g.dart index ac58d6c59..becc54084 100644 --- a/mobile-app-new/lib/podcasts/repositories/download_repository.g.dart +++ b/mobile-app-new/lib/podcasts/repositories/download_repository.g.dart @@ -55,4 +55,4 @@ final class PodcastDownloadRepositoryProvider } String _$podcastDownloadRepositoryHash() => - r'15405ac28c4d7d71680f781d534431e409120a6b'; + r'193b2f76ef7fed2fd55ec1ad0f8801b94f189373'; diff --git a/mobile-app-new/lib/services/dio_service.dart b/mobile-app-new/lib/services/dio_service.dart index 596469dc6..9f2dae7ca 100644 --- a/mobile-app-new/lib/services/dio_service.dart +++ b/mobile-app-new/lib/services/dio_service.dart @@ -1,23 +1,19 @@ import 'package:dio/dio.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -class DioService { - static final DioService _dioService = DioService._internal(); +part 'dio_service.g.dart'; - static final Dio dio = Dio(); +@Riverpod(keepAlive: true) +Dio dio(Ref ref) { + final dio = Dio(); - factory DioService() { - return _dioService; + if (dotenv.getBool('DEVELOPMENT_MODE', fallback: false)) { + dio.interceptors.add(PrettyDioLogger(responseBody: false)); } - Future init() async { - bool isDevMode = dotenv.getBool('DEVELOPMENT_MODE', fallback: false); + ref.onDispose(dio.close); - if (isDevMode) { - dio.interceptors.add(PrettyDioLogger(responseBody: false)); - } - } - - DioService._internal(); + return dio; } diff --git a/mobile-app-new/lib/services/dio_service.g.dart b/mobile-app-new/lib/services/dio_service.g.dart new file mode 100644 index 000000000..722bf4995 --- /dev/null +++ b/mobile-app-new/lib/services/dio_service.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dio_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(dio) +final dioProvider = DioProvider._(); + +final class DioProvider extends $FunctionalProvider + with $Provider { + DioProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'dioProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$dioHash(); + + @$internal + @override + $ProviderElement $createElement($ProviderPointer pointer) => + $ProviderElement(pointer); + + @override + Dio create(Ref ref) { + return dio(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(Dio value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$dioHash() => r'0ec5d203ab21d291bb4a2a4ad994085c797a960c'; From 90b44a48b92a54fb0f2b791c651fd0a812000964 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 30 Sep 2026 20:46:32 +0530 Subject: [PATCH 35/52] chore: missing semantic label for podcast player buttons --- .../lib/podcasts/ui/episode/episode_view.dart | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/mobile-app-new/lib/podcasts/ui/episode/episode_view.dart b/mobile-app-new/lib/podcasts/ui/episode/episode_view.dart index 3c1134e2c..3b6ce99f9 100644 --- a/mobile-app-new/lib/podcasts/ui/episode/episode_view.dart +++ b/mobile-app-new/lib/podcasts/ui/episode/episode_view.dart @@ -246,7 +246,10 @@ class _Controls extends ConsumerWidget { const SizedBox(width: 8), IconButton( iconSize: 45, - icon: const Icon(Icons.replay_10_rounded), + icon: const Icon( + Icons.replay_10_rounded, + semanticLabel: 'Rewind 10 seconds', + ), onPressed: () => ref.read(podcastPlayerProvider.notifier).rewind(), ), IconButton( @@ -264,11 +267,19 @@ class _Controls extends ConsumerWidget { ), ), ) - : Icon(status.isPlaying ? Icons.pause : Icons.play_arrow_rounded), + : Icon( + status.isPlaying ? Icons.pause : Icons.play_arrow_rounded, + semanticLabel: status.isPlaying + ? 'Pause episode' + : 'Play episode', + ), ), IconButton( iconSize: 45, - icon: const Icon(Icons.forward_30_rounded), + icon: const Icon( + Icons.forward_30_rounded, + semanticLabel: 'Fast forward 30 seconds', + ), onPressed: () => ref.read(podcastPlayerProvider.notifier).fastForward(), ), From 47def013110e9feb1a5d251bdfe61ffc62616b78 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 11 Oct 2026 12:29:52 +0530 Subject: [PATCH 36/52] Learn 1/4 --- mobile-app-new/android/app/build.gradle.kts | 4 + mobile-app-new/assets/images/apple-logo.png | Bin 0 -> 54493 bytes mobile-app-new/assets/images/github-logo.png | Bin 0 -> 6393 bytes mobile-app-new/assets/images/google-logo.png | Bin 0 -> 122573 bytes mobile-app-new/assets/images/logo.png | Bin 0 -> 25521 bytes .../ios/Runner.xcodeproj/project.pbxproj | 18 +- .../xcshareddata/swiftpm/Package.resolved | 32 + .../xcshareddata/swiftpm/Package.resolved | 32 + mobile-app-new/ios/Runner/Info.plist | 13 + mobile-app-new/ios/Runner/Runner.entitlements | 11 + .../lib/auth/models/user_model.dart | 141 ++ .../lib/auth/models/user_model.freezed.dart | 1551 +++++++++++++++++ .../lib/auth/models/user_model.g.dart | 205 +++ .../auth/repositories/session_repository.dart | 171 ++ .../repositories/session_repository.g.dart | 58 + .../lib/auth/services/session_service.dart | 103 ++ .../lib/auth/services/session_service.g.dart | 58 + .../lib/auth/ui/login/login_view.dart | 294 ++++ .../lib/auth/ui/login/login_viewmodel.dart | 77 + .../ui/login/login_viewmodel.freezed.dart | 277 +++ .../lib/auth/ui/login/login_viewmodel.g.dart | 62 + .../lib/auth/ui/profile/profile_view.dart | 606 +++++++ .../auth/ui/profile/profile_viewmodel.dart | 9 + .../auth/ui/profile/profile_viewmodel.g.dart | 43 + .../learn/models/challenge_file_model.dart | 26 + .../models/challenge_file_model.freezed.dart | 310 ++++ .../learn/models/challenge_file_model.g.dart | 47 + mobile-app-new/lib/main.dart | 3 + mobile-app-new/lib/routing/auth.dart | 26 + mobile-app-new/lib/routing/auth.g.dart | 61 + mobile-app-new/lib/routing/router.dart | 4 + mobile-app-new/lib/routing/settings.dart | 28 + mobile-app-new/lib/routing/settings.g.dart | 62 + mobile-app-new/lib/services/auth_service.dart | 59 + .../lib/services/auth_service.g.dart | 54 + .../delete_account/delete_account_view.dart | 117 ++ .../delete_account_viewmodel.dart | 27 + .../delete_account_viewmodel.g.dart | 63 + .../settings/ui/settings/settings_view.dart | 73 + mobile-app-new/lib/utils/custom_tab.dart | 18 + .../lib/widgets/dialog/confirm_dialog.dart | 82 + mobile-app-new/lib/widgets/drawer/drawer.dart | 151 +- .../lib/widgets/drawer/scroll_shadow.dart | 75 + .../lib/widgets/section_divider.dart | 8 + mobile-app-new/pubspec.lock | 112 ++ mobile-app-new/pubspec.yaml | 4 + 46 files changed, 5147 insertions(+), 28 deletions(-) create mode 100644 mobile-app-new/assets/images/apple-logo.png create mode 100644 mobile-app-new/assets/images/github-logo.png create mode 100644 mobile-app-new/assets/images/google-logo.png create mode 100644 mobile-app-new/assets/images/logo.png create mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 mobile-app-new/ios/Runner/Runner.entitlements create mode 100644 mobile-app-new/lib/auth/models/user_model.dart create mode 100644 mobile-app-new/lib/auth/models/user_model.freezed.dart create mode 100644 mobile-app-new/lib/auth/models/user_model.g.dart create mode 100644 mobile-app-new/lib/auth/repositories/session_repository.dart create mode 100644 mobile-app-new/lib/auth/repositories/session_repository.g.dart create mode 100644 mobile-app-new/lib/auth/services/session_service.dart create mode 100644 mobile-app-new/lib/auth/services/session_service.g.dart create mode 100644 mobile-app-new/lib/auth/ui/login/login_view.dart create mode 100644 mobile-app-new/lib/auth/ui/login/login_viewmodel.dart create mode 100644 mobile-app-new/lib/auth/ui/login/login_viewmodel.freezed.dart create mode 100644 mobile-app-new/lib/auth/ui/login/login_viewmodel.g.dart create mode 100644 mobile-app-new/lib/auth/ui/profile/profile_view.dart create mode 100644 mobile-app-new/lib/auth/ui/profile/profile_viewmodel.dart create mode 100644 mobile-app-new/lib/auth/ui/profile/profile_viewmodel.g.dart create mode 100644 mobile-app-new/lib/learn/models/challenge_file_model.dart create mode 100644 mobile-app-new/lib/learn/models/challenge_file_model.freezed.dart create mode 100644 mobile-app-new/lib/learn/models/challenge_file_model.g.dart create mode 100644 mobile-app-new/lib/routing/auth.dart create mode 100644 mobile-app-new/lib/routing/auth.g.dart create mode 100644 mobile-app-new/lib/routing/settings.dart create mode 100644 mobile-app-new/lib/routing/settings.g.dart create mode 100644 mobile-app-new/lib/services/auth_service.dart create mode 100644 mobile-app-new/lib/services/auth_service.g.dart create mode 100644 mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_view.dart create mode 100644 mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.dart create mode 100644 mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.g.dart create mode 100644 mobile-app-new/lib/settings/ui/settings/settings_view.dart create mode 100644 mobile-app-new/lib/utils/custom_tab.dart create mode 100644 mobile-app-new/lib/widgets/dialog/confirm_dialog.dart create mode 100644 mobile-app-new/lib/widgets/drawer/scroll_shadow.dart create mode 100644 mobile-app-new/lib/widgets/section_divider.dart diff --git a/mobile-app-new/android/app/build.gradle.kts b/mobile-app-new/android/app/build.gradle.kts index 7fc8cdee3..535d61d30 100644 --- a/mobile-app-new/android/app/build.gradle.kts +++ b/mobile-app-new/android/app/build.gradle.kts @@ -24,6 +24,8 @@ android { targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName + manifestPlaceholders["auth0Domain"] = "freecodecamp-dev.auth0.com" + manifestPlaceholders["auth0Scheme"] = "org.freecodecamp" } buildTypes { @@ -31,6 +33,8 @@ android { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") + manifestPlaceholders["auth0Domain"] = "auth.freecodecamp.org" + manifestPlaceholders["auth0Scheme"] = "https" } } } diff --git a/mobile-app-new/assets/images/apple-logo.png b/mobile-app-new/assets/images/apple-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7c5fd14f39e540e0a4fd11cbef2a2acd26578a01 GIT binary patch literal 54493 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 literal 0 HcmV?d00001 diff --git a/mobile-app-new/assets/images/github-logo.png b/mobile-app-new/assets/images/github-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb3b705d018006a2bd4200ea94c9d5fb98b6f76 GIT binary patch literal 6393 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 Hu0mjfZ$b4` literal 0 HcmV?d00001 diff --git a/mobile-app-new/assets/images/google-logo.png b/mobile-app-new/assets/images/google-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1c18833a858410ac67344084ce02a1aa3b8e9aec GIT binary patch literal 122573 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 literal 0 HcmV?d00001 diff --git a/mobile-app-new/assets/images/logo.png b/mobile-app-new/assets/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..375c9e295c89c1d844185763daa31a7a67c26e9d GIT binary patch literal 25521 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 literal 0 HcmV?d00001 diff --git a/mobile-app-new/ios/Runner.xcodeproj/project.pbxproj b/mobile-app-new/ios/Runner.xcodeproj/project.pbxproj index c94a77c31..a3c44e0d0 100644 --- a/mobile-app-new/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile-app-new/ios/Runner.xcodeproj/project.pbxproj @@ -375,15 +375,17 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = L33K9LWVP9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.ios; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -399,7 +401,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.ios.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -416,7 +418,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.ios.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -431,7 +433,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.ios.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -555,15 +557,17 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = L33K9LWVP9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.ios; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -578,15 +582,17 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = L33K9LWVP9; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.mobileAppNew; + PRODUCT_BUNDLE_IDENTIFIER = org.freecodecamp.ios; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..c179faa26 --- /dev/null +++ b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,32 @@ +{ + "pins" : [ + { + "identity" : "auth0.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/Auth0.swift", + "state" : { + "revision" : "47db336fdd56bddecb3c95a69264d5c1fd0c35b4", + "version" : "2.24.1" + } + }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, + { + "identity" : "simplekeychain", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/SimpleKeychain", + "state" : { + "revision" : "776c4a6db74d5c6c143974be91c383680d468630", + "version" : "1.3.0" + } + } + ], + "version" : 2 +} diff --git a/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..c179faa26 --- /dev/null +++ b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,32 @@ +{ + "pins" : [ + { + "identity" : "auth0.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/Auth0.swift", + "state" : { + "revision" : "47db336fdd56bddecb3c95a69264d5c1fd0c35b4", + "version" : "2.24.1" + } + }, + { + "identity" : "jwtdecode.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/JWTDecode.swift", + "state" : { + "revision" : "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version" : "3.3.0" + } + }, + { + "identity" : "simplekeychain", + "kind" : "remoteSourceControl", + "location" : "https://github.com/auth0/SimpleKeychain", + "state" : { + "revision" : "776c4a6db74d5c6c143974be91c383680d468630", + "version" : "1.3.0" + } + } + ], + "version" : 2 +} diff --git a/mobile-app-new/ios/Runner/Info.plist b/mobile-app-new/ios/Runner/Info.plist index f12214725..8d15d6c37 100644 --- a/mobile-app-new/ios/Runner/Info.plist +++ b/mobile-app-new/ios/Runner/Info.plist @@ -22,6 +22,19 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? + CFBundleURLTypes + + + CFBundleTypeRole + None + CFBundleURLName + auth0 + CFBundleURLSchemes + + $(PRODUCT_BUNDLE_IDENTIFIER) + + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS diff --git a/mobile-app-new/ios/Runner/Runner.entitlements b/mobile-app-new/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..c414f13ff --- /dev/null +++ b/mobile-app-new/ios/Runner/Runner.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.developer.associated-domains + + webcredentials:auth.freecodecamp.org + webcredentials:freecodecamp-dev.auth0.com + + + diff --git a/mobile-app-new/lib/auth/models/user_model.dart b/mobile-app-new/lib/auth/models/user_model.dart new file mode 100644 index 000000000..b03baec84 --- /dev/null +++ b/mobile-app-new/lib/auth/models/user_model.dart @@ -0,0 +1,141 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/learn/models/challenge_file_model.dart'; + +part 'user_model.freezed.dart'; +part 'user_model.g.dart'; + +DateTime _fromEpochMs(int ms) => DateTime.fromMillisecondsSinceEpoch(ms); +int _toEpochMs(DateTime date) => date.millisecondsSinceEpoch; + +// NOTE: The API sends a mix of numbers and strings. +List _yearsToStrings(List years) => + years.map((year) => year.toString()).toList(); + +@freezed +abstract class Portfolio with _$Portfolio { + const factory Portfolio({ + required String id, + String? title, + String? url, + String? image, + String? description, + }) = _Portfolio; + + factory Portfolio.fromJson(Map json) => + _$PortfolioFromJson(json); +} + +@freezed +abstract class CompletedChallenge with _$CompletedChallenge { + const factory CompletedChallenge({ + required String id, + String? solution, + String? githubLink, + int? challengeType, + @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) + required DateTime completedDate, + @Default([]) List files, + }) = _CompletedChallenge; + + factory CompletedChallenge.fromJson(Map json) => + _$CompletedChallengeFromJson(json); +} + +@freezed +abstract class CompletedDailyChallenge with _$CompletedDailyChallenge { + const factory CompletedDailyChallenge({ + required String id, + @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) + required DateTime completedDate, + @Default([]) List languages, + }) = _CompletedDailyChallenge; + + factory CompletedDailyChallenge.fromJson(Map json) => + _$CompletedDailyChallengeFromJson(json); +} + +@freezed +abstract class SavedChallenge with _$SavedChallenge { + const factory SavedChallenge({ + required String id, + @Default([]) List files, + }) = _SavedChallenge; + + factory SavedChallenge.fromJson(Map json) => + _$SavedChallengeFromJson(json); +} + +@freezed +abstract class FccUserModel with _$FccUserModel { + const FccUserModel._(); + + const factory FccUserModel({ + required String id, + required String email, + required String username, + @Default('') String name, + @Default('') String picture, + String? currentChallengeId, + String? githubProfile, + String? linkedin, + String? twitter, + String? website, + String? location, + String? about, + @Default(false) bool emailVerified, + @Default(false) bool isEmailVerified, + @Default(false) bool isCheater, + @Default(false) bool isDonating, + @Default(false) bool isHonest, + @Default(false) bool isBanned, + @Default(false) bool isFrontEndCert, + @Default(false) bool isDataVisCert, + @Default(false) bool isBackEndCert, + @Default(false) bool isFullStackCert, + @Default(false) bool isRespWebDesignCert, + @Default(false) bool is2018DataVisCert, + @Default(false) bool isFrontEndLibsCert, + @Default(false) bool isJsAlgoDataStructCert, + @Default(false) bool isApisMicroservicesCert, + @Default(false) bool isInfosecQaCert, + @Default(false) bool isQaCertV7, + @Default(false) bool isInfosecCertV7, + @Default(false) bool is2018FullStackCert, + @Default(false) bool isSciCompPyCertV7, + @Default(false) bool isDataAnalysisPyCertV7, + @Default(false) bool isMachineLearningPyCertV7, + @Default(false) bool isRelationalDatabaseCertV8, + @Default(false) bool isCollegeAlgebraPyCertV8, + @Default(false) bool isFoundationalCSharpCertV8, + required DateTime joinDate, + @Default(0) int points, + // NOTE: Keys are epoch seconds as strings, plus a stray 'NaN' key. + @Default({}) Map calendar, + @Default([]) List completedChallenges, + @Default([]) List completedDailyCodingChallenges, + @Default([]) List savedChallenges, + @Default([]) List portfolio, + @JsonKey(fromJson: _yearsToStrings) + @Default([]) + List yearsTopContributor, + }) = _FccUserModel; + + factory FccUserModel.fromJson(Map json) => + _$FccUserModelFromJson(json); + + // Points per local calendar day, for the heatmap and streaks. + Map get heatMapCal { + final days = {}; + + for (final MapEntry(:key, :value) in calendar.entries) { + final seconds = int.tryParse(key); + if (seconds == null) continue; + + final time = _fromEpochMs(seconds * 1000); + final day = DateTime(time.year, time.month, time.day); + days[day] = (days[day] ?? 0) + value; + } + + return days; + } +} diff --git a/mobile-app-new/lib/auth/models/user_model.freezed.dart b/mobile-app-new/lib/auth/models/user_model.freezed.dart new file mode 100644 index 000000000..16e7eae7c --- /dev/null +++ b/mobile-app-new/lib/auth/models/user_model.freezed.dart @@ -0,0 +1,1551 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'user_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Portfolio { + + String get id; String? get title; String? get url; String? get image; String? get description; +/// Create a copy of Portfolio +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PortfolioCopyWith get copyWith => _$PortfolioCopyWithImpl(this as Portfolio, _$identity); + + /// Serializes this Portfolio to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Portfolio&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.image, image) || other.image == image)&&(identical(other.description, description) || other.description == description)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,title,url,image,description); + +@override +String toString() { + return 'Portfolio(id: $id, title: $title, url: $url, image: $image, description: $description)'; +} + + +} + +/// @nodoc +abstract mixin class $PortfolioCopyWith<$Res> { + factory $PortfolioCopyWith(Portfolio value, $Res Function(Portfolio) _then) = _$PortfolioCopyWithImpl; +@useResult +$Res call({ + String id, String? title, String? url, String? image, String? description +}); + + + + +} +/// @nodoc +class _$PortfolioCopyWithImpl<$Res> + implements $PortfolioCopyWith<$Res> { + _$PortfolioCopyWithImpl(this._self, this._then); + + final Portfolio _self; + final $Res Function(Portfolio) _then; + +/// Create a copy of Portfolio +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = freezed,Object? url = freezed,Object? image = freezed,Object? description = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Portfolio]. +extension PortfolioPatterns on Portfolio { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Portfolio value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Portfolio() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Portfolio value) $default,){ +final _that = this; +switch (_that) { +case _Portfolio(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Portfolio value)? $default,){ +final _that = this; +switch (_that) { +case _Portfolio() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String? title, String? url, String? image, String? description)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Portfolio() when $default != null: +return $default(_that.id,_that.title,_that.url,_that.image,_that.description);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String? title, String? url, String? image, String? description) $default,) {final _that = this; +switch (_that) { +case _Portfolio(): +return $default(_that.id,_that.title,_that.url,_that.image,_that.description);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String? title, String? url, String? image, String? description)? $default,) {final _that = this; +switch (_that) { +case _Portfolio() when $default != null: +return $default(_that.id,_that.title,_that.url,_that.image,_that.description);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Portfolio implements Portfolio { + const _Portfolio({required this.id, this.title, this.url, this.image, this.description}); + factory _Portfolio.fromJson(Map json) => _$PortfolioFromJson(json); + +@override final String id; +@override final String? title; +@override final String? url; +@override final String? image; +@override final String? description; + +/// Create a copy of Portfolio +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PortfolioCopyWith<_Portfolio> get copyWith => __$PortfolioCopyWithImpl<_Portfolio>(this, _$identity); + +@override +Map toJson() { + return _$PortfolioToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Portfolio&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.image, image) || other.image == image)&&(identical(other.description, description) || other.description == description)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,title,url,image,description); + +@override +String toString() { + return 'Portfolio(id: $id, title: $title, url: $url, image: $image, description: $description)'; +} + + +} + +/// @nodoc +abstract mixin class _$PortfolioCopyWith<$Res> implements $PortfolioCopyWith<$Res> { + factory _$PortfolioCopyWith(_Portfolio value, $Res Function(_Portfolio) _then) = __$PortfolioCopyWithImpl; +@override @useResult +$Res call({ + String id, String? title, String? url, String? image, String? description +}); + + + + +} +/// @nodoc +class __$PortfolioCopyWithImpl<$Res> + implements _$PortfolioCopyWith<$Res> { + __$PortfolioCopyWithImpl(this._self, this._then); + + final _Portfolio _self; + final $Res Function(_Portfolio) _then; + +/// Create a copy of Portfolio +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = freezed,Object? url = freezed,Object? image = freezed,Object? description = freezed,}) { + return _then(_Portfolio( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String?,url: freezed == url ? _self.url : url // ignore: cast_nullable_to_non_nullable +as String?,image: freezed == image ? _self.image : image // ignore: cast_nullable_to_non_nullable +as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$CompletedChallenge { + + String get id; String? get solution; String? get githubLink; int? get challengeType;@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime get completedDate; List get files; +/// Create a copy of CompletedChallenge +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CompletedChallengeCopyWith get copyWith => _$CompletedChallengeCopyWithImpl(this as CompletedChallenge, _$identity); + + /// Serializes this CompletedChallenge to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CompletedChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.solution, solution) || other.solution == solution)&&(identical(other.githubLink, githubLink) || other.githubLink == githubLink)&&(identical(other.challengeType, challengeType) || other.challengeType == challengeType)&&(identical(other.completedDate, completedDate) || other.completedDate == completedDate)&&const DeepCollectionEquality().equals(other.files, files)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,solution,githubLink,challengeType,completedDate,const DeepCollectionEquality().hash(files)); + +@override +String toString() { + return 'CompletedChallenge(id: $id, solution: $solution, githubLink: $githubLink, challengeType: $challengeType, completedDate: $completedDate, files: $files)'; +} + + +} + +/// @nodoc +abstract mixin class $CompletedChallengeCopyWith<$Res> { + factory $CompletedChallengeCopyWith(CompletedChallenge value, $Res Function(CompletedChallenge) _then) = _$CompletedChallengeCopyWithImpl; +@useResult +$Res call({ + String id, String? solution, String? githubLink, int? challengeType,@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List files +}); + + + + +} +/// @nodoc +class _$CompletedChallengeCopyWithImpl<$Res> + implements $CompletedChallengeCopyWith<$Res> { + _$CompletedChallengeCopyWithImpl(this._self, this._then); + + final CompletedChallenge _self; + final $Res Function(CompletedChallenge) _then; + +/// Create a copy of CompletedChallenge +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? solution = freezed,Object? githubLink = freezed,Object? challengeType = freezed,Object? completedDate = null,Object? files = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,solution: freezed == solution ? _self.solution : solution // ignore: cast_nullable_to_non_nullable +as String?,githubLink: freezed == githubLink ? _self.githubLink : githubLink // ignore: cast_nullable_to_non_nullable +as String?,challengeType: freezed == challengeType ? _self.challengeType : challengeType // ignore: cast_nullable_to_non_nullable +as int?,completedDate: null == completedDate ? _self.completedDate : completedDate // ignore: cast_nullable_to_non_nullable +as DateTime,files: null == files ? _self.files : files // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CompletedChallenge]. +extension CompletedChallengePatterns on CompletedChallenge { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CompletedChallenge value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CompletedChallenge() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CompletedChallenge value) $default,){ +final _that = this; +switch (_that) { +case _CompletedChallenge(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CompletedChallenge value)? $default,){ +final _that = this; +switch (_that) { +case _CompletedChallenge() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String? solution, String? githubLink, int? challengeType, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List files)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CompletedChallenge() when $default != null: +return $default(_that.id,_that.solution,_that.githubLink,_that.challengeType,_that.completedDate,_that.files);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String? solution, String? githubLink, int? challengeType, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List files) $default,) {final _that = this; +switch (_that) { +case _CompletedChallenge(): +return $default(_that.id,_that.solution,_that.githubLink,_that.challengeType,_that.completedDate,_that.files);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String? solution, String? githubLink, int? challengeType, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List files)? $default,) {final _that = this; +switch (_that) { +case _CompletedChallenge() when $default != null: +return $default(_that.id,_that.solution,_that.githubLink,_that.challengeType,_that.completedDate,_that.files);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CompletedChallenge implements CompletedChallenge { + const _CompletedChallenge({required this.id, this.solution, this.githubLink, this.challengeType, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) required this.completedDate, final List files = const []}): _files = files; + factory _CompletedChallenge.fromJson(Map json) => _$CompletedChallengeFromJson(json); + +@override final String id; +@override final String? solution; +@override final String? githubLink; +@override final int? challengeType; +@override@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) final DateTime completedDate; + final List _files; +@override@JsonKey() List get files { + if (_files is EqualUnmodifiableListView) return _files; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_files); +} + + +/// Create a copy of CompletedChallenge +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CompletedChallengeCopyWith<_CompletedChallenge> get copyWith => __$CompletedChallengeCopyWithImpl<_CompletedChallenge>(this, _$identity); + +@override +Map toJson() { + return _$CompletedChallengeToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CompletedChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.solution, solution) || other.solution == solution)&&(identical(other.githubLink, githubLink) || other.githubLink == githubLink)&&(identical(other.challengeType, challengeType) || other.challengeType == challengeType)&&(identical(other.completedDate, completedDate) || other.completedDate == completedDate)&&const DeepCollectionEquality().equals(other._files, _files)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,solution,githubLink,challengeType,completedDate,const DeepCollectionEquality().hash(_files)); + +@override +String toString() { + return 'CompletedChallenge(id: $id, solution: $solution, githubLink: $githubLink, challengeType: $challengeType, completedDate: $completedDate, files: $files)'; +} + + +} + +/// @nodoc +abstract mixin class _$CompletedChallengeCopyWith<$Res> implements $CompletedChallengeCopyWith<$Res> { + factory _$CompletedChallengeCopyWith(_CompletedChallenge value, $Res Function(_CompletedChallenge) _then) = __$CompletedChallengeCopyWithImpl; +@override @useResult +$Res call({ + String id, String? solution, String? githubLink, int? challengeType,@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List files +}); + + + + +} +/// @nodoc +class __$CompletedChallengeCopyWithImpl<$Res> + implements _$CompletedChallengeCopyWith<$Res> { + __$CompletedChallengeCopyWithImpl(this._self, this._then); + + final _CompletedChallenge _self; + final $Res Function(_CompletedChallenge) _then; + +/// Create a copy of CompletedChallenge +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? solution = freezed,Object? githubLink = freezed,Object? challengeType = freezed,Object? completedDate = null,Object? files = null,}) { + return _then(_CompletedChallenge( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,solution: freezed == solution ? _self.solution : solution // ignore: cast_nullable_to_non_nullable +as String?,githubLink: freezed == githubLink ? _self.githubLink : githubLink // ignore: cast_nullable_to_non_nullable +as String?,challengeType: freezed == challengeType ? _self.challengeType : challengeType // ignore: cast_nullable_to_non_nullable +as int?,completedDate: null == completedDate ? _self.completedDate : completedDate // ignore: cast_nullable_to_non_nullable +as DateTime,files: null == files ? _self._files : files // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$CompletedDailyChallenge { + + String get id;@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime get completedDate; List get languages; +/// Create a copy of CompletedDailyChallenge +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CompletedDailyChallengeCopyWith get copyWith => _$CompletedDailyChallengeCopyWithImpl(this as CompletedDailyChallenge, _$identity); + + /// Serializes this CompletedDailyChallenge to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CompletedDailyChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.completedDate, completedDate) || other.completedDate == completedDate)&&const DeepCollectionEquality().equals(other.languages, languages)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,completedDate,const DeepCollectionEquality().hash(languages)); + +@override +String toString() { + return 'CompletedDailyChallenge(id: $id, completedDate: $completedDate, languages: $languages)'; +} + + +} + +/// @nodoc +abstract mixin class $CompletedDailyChallengeCopyWith<$Res> { + factory $CompletedDailyChallengeCopyWith(CompletedDailyChallenge value, $Res Function(CompletedDailyChallenge) _then) = _$CompletedDailyChallengeCopyWithImpl; +@useResult +$Res call({ + String id,@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List languages +}); + + + + +} +/// @nodoc +class _$CompletedDailyChallengeCopyWithImpl<$Res> + implements $CompletedDailyChallengeCopyWith<$Res> { + _$CompletedDailyChallengeCopyWithImpl(this._self, this._then); + + final CompletedDailyChallenge _self; + final $Res Function(CompletedDailyChallenge) _then; + +/// Create a copy of CompletedDailyChallenge +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? completedDate = null,Object? languages = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,completedDate: null == completedDate ? _self.completedDate : completedDate // ignore: cast_nullable_to_non_nullable +as DateTime,languages: null == languages ? _self.languages : languages // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CompletedDailyChallenge]. +extension CompletedDailyChallengePatterns on CompletedDailyChallenge { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CompletedDailyChallenge value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CompletedDailyChallenge() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CompletedDailyChallenge value) $default,){ +final _that = this; +switch (_that) { +case _CompletedDailyChallenge(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CompletedDailyChallenge value)? $default,){ +final _that = this; +switch (_that) { +case _CompletedDailyChallenge() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List languages)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CompletedDailyChallenge() when $default != null: +return $default(_that.id,_that.completedDate,_that.languages);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List languages) $default,) {final _that = this; +switch (_that) { +case _CompletedDailyChallenge(): +return $default(_that.id,_that.completedDate,_that.languages);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List languages)? $default,) {final _that = this; +switch (_that) { +case _CompletedDailyChallenge() when $default != null: +return $default(_that.id,_that.completedDate,_that.languages);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CompletedDailyChallenge implements CompletedDailyChallenge { + const _CompletedDailyChallenge({required this.id, @JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) required this.completedDate, final List languages = const []}): _languages = languages; + factory _CompletedDailyChallenge.fromJson(Map json) => _$CompletedDailyChallengeFromJson(json); + +@override final String id; +@override@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) final DateTime completedDate; + final List _languages; +@override@JsonKey() List get languages { + if (_languages is EqualUnmodifiableListView) return _languages; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_languages); +} + + +/// Create a copy of CompletedDailyChallenge +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CompletedDailyChallengeCopyWith<_CompletedDailyChallenge> get copyWith => __$CompletedDailyChallengeCopyWithImpl<_CompletedDailyChallenge>(this, _$identity); + +@override +Map toJson() { + return _$CompletedDailyChallengeToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CompletedDailyChallenge&&(identical(other.id, id) || other.id == id)&&(identical(other.completedDate, completedDate) || other.completedDate == completedDate)&&const DeepCollectionEquality().equals(other._languages, _languages)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,completedDate,const DeepCollectionEquality().hash(_languages)); + +@override +String toString() { + return 'CompletedDailyChallenge(id: $id, completedDate: $completedDate, languages: $languages)'; +} + + +} + +/// @nodoc +abstract mixin class _$CompletedDailyChallengeCopyWith<$Res> implements $CompletedDailyChallengeCopyWith<$Res> { + factory _$CompletedDailyChallengeCopyWith(_CompletedDailyChallenge value, $Res Function(_CompletedDailyChallenge) _then) = __$CompletedDailyChallengeCopyWithImpl; +@override @useResult +$Res call({ + String id,@JsonKey(fromJson: _fromEpochMs, toJson: _toEpochMs) DateTime completedDate, List languages +}); + + + + +} +/// @nodoc +class __$CompletedDailyChallengeCopyWithImpl<$Res> + implements _$CompletedDailyChallengeCopyWith<$Res> { + __$CompletedDailyChallengeCopyWithImpl(this._self, this._then); + + final _CompletedDailyChallenge _self; + final $Res Function(_CompletedDailyChallenge) _then; + +/// Create a copy of CompletedDailyChallenge +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? completedDate = null,Object? languages = null,}) { + return _then(_CompletedDailyChallenge( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,completedDate: null == completedDate ? _self.completedDate : completedDate // ignore: cast_nullable_to_non_nullable +as DateTime,languages: null == languages ? _self._languages : languages // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$SavedChallenge { + + String get id; List get files; +/// Create a copy of SavedChallenge +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SavedChallengeCopyWith get copyWith => _$SavedChallengeCopyWithImpl(this as SavedChallenge, _$identity); + + /// Serializes this SavedChallenge to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SavedChallenge&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other.files, files)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(files)); + +@override +String toString() { + return 'SavedChallenge(id: $id, files: $files)'; +} + + +} + +/// @nodoc +abstract mixin class $SavedChallengeCopyWith<$Res> { + factory $SavedChallengeCopyWith(SavedChallenge value, $Res Function(SavedChallenge) _then) = _$SavedChallengeCopyWithImpl; +@useResult +$Res call({ + String id, List files +}); + + + + +} +/// @nodoc +class _$SavedChallengeCopyWithImpl<$Res> + implements $SavedChallengeCopyWith<$Res> { + _$SavedChallengeCopyWithImpl(this._self, this._then); + + final SavedChallenge _self; + final $Res Function(SavedChallenge) _then; + +/// Create a copy of SavedChallenge +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? files = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,files: null == files ? _self.files : files // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SavedChallenge]. +extension SavedChallengePatterns on SavedChallenge { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SavedChallenge value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SavedChallenge() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SavedChallenge value) $default,){ +final _that = this; +switch (_that) { +case _SavedChallenge(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SavedChallenge value)? $default,){ +final _that = this; +switch (_that) { +case _SavedChallenge() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, List files)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SavedChallenge() when $default != null: +return $default(_that.id,_that.files);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, List files) $default,) {final _that = this; +switch (_that) { +case _SavedChallenge(): +return $default(_that.id,_that.files);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, List files)? $default,) {final _that = this; +switch (_that) { +case _SavedChallenge() when $default != null: +return $default(_that.id,_that.files);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SavedChallenge implements SavedChallenge { + const _SavedChallenge({required this.id, final List files = const []}): _files = files; + factory _SavedChallenge.fromJson(Map json) => _$SavedChallengeFromJson(json); + +@override final String id; + final List _files; +@override@JsonKey() List get files { + if (_files is EqualUnmodifiableListView) return _files; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_files); +} + + +/// Create a copy of SavedChallenge +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SavedChallengeCopyWith<_SavedChallenge> get copyWith => __$SavedChallengeCopyWithImpl<_SavedChallenge>(this, _$identity); + +@override +Map toJson() { + return _$SavedChallengeToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SavedChallenge&&(identical(other.id, id) || other.id == id)&&const DeepCollectionEquality().equals(other._files, _files)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,const DeepCollectionEquality().hash(_files)); + +@override +String toString() { + return 'SavedChallenge(id: $id, files: $files)'; +} + + +} + +/// @nodoc +abstract mixin class _$SavedChallengeCopyWith<$Res> implements $SavedChallengeCopyWith<$Res> { + factory _$SavedChallengeCopyWith(_SavedChallenge value, $Res Function(_SavedChallenge) _then) = __$SavedChallengeCopyWithImpl; +@override @useResult +$Res call({ + String id, List files +}); + + + + +} +/// @nodoc +class __$SavedChallengeCopyWithImpl<$Res> + implements _$SavedChallengeCopyWith<$Res> { + __$SavedChallengeCopyWithImpl(this._self, this._then); + + final _SavedChallenge _self; + final $Res Function(_SavedChallenge) _then; + +/// Create a copy of SavedChallenge +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? files = null,}) { + return _then(_SavedChallenge( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,files: null == files ? _self._files : files // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$FccUserModel { + + String get id; String get email; String get username; String get name; String get picture; String? get currentChallengeId; String? get githubProfile; String? get linkedin; String? get twitter; String? get website; String? get location; String? get about; bool get emailVerified; bool get isEmailVerified; bool get isCheater; bool get isDonating; bool get isHonest; bool get isBanned; bool get isFrontEndCert; bool get isDataVisCert; bool get isBackEndCert; bool get isFullStackCert; bool get isRespWebDesignCert; bool get is2018DataVisCert; bool get isFrontEndLibsCert; bool get isJsAlgoDataStructCert; bool get isApisMicroservicesCert; bool get isInfosecQaCert; bool get isQaCertV7; bool get isInfosecCertV7; bool get is2018FullStackCert; bool get isSciCompPyCertV7; bool get isDataAnalysisPyCertV7; bool get isMachineLearningPyCertV7; bool get isRelationalDatabaseCertV8; bool get isCollegeAlgebraPyCertV8; bool get isFoundationalCSharpCertV8; DateTime get joinDate; int get points; Map get calendar; List get completedChallenges; List get completedDailyCodingChallenges; List get savedChallenges; List get portfolio;@JsonKey(fromJson: _yearsToStrings) List get yearsTopContributor; +/// Create a copy of FccUserModel +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$FccUserModelCopyWith get copyWith => _$FccUserModelCopyWithImpl(this as FccUserModel, _$identity); + + /// Serializes this FccUserModel to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is FccUserModel&&(identical(other.id, id) || other.id == id)&&(identical(other.email, email) || other.email == email)&&(identical(other.username, username) || other.username == username)&&(identical(other.name, name) || other.name == name)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.currentChallengeId, currentChallengeId) || other.currentChallengeId == currentChallengeId)&&(identical(other.githubProfile, githubProfile) || other.githubProfile == githubProfile)&&(identical(other.linkedin, linkedin) || other.linkedin == linkedin)&&(identical(other.twitter, twitter) || other.twitter == twitter)&&(identical(other.website, website) || other.website == website)&&(identical(other.location, location) || other.location == location)&&(identical(other.about, about) || other.about == about)&&(identical(other.emailVerified, emailVerified) || other.emailVerified == emailVerified)&&(identical(other.isEmailVerified, isEmailVerified) || other.isEmailVerified == isEmailVerified)&&(identical(other.isCheater, isCheater) || other.isCheater == isCheater)&&(identical(other.isDonating, isDonating) || other.isDonating == isDonating)&&(identical(other.isHonest, isHonest) || other.isHonest == isHonest)&&(identical(other.isBanned, isBanned) || other.isBanned == isBanned)&&(identical(other.isFrontEndCert, isFrontEndCert) || other.isFrontEndCert == isFrontEndCert)&&(identical(other.isDataVisCert, isDataVisCert) || other.isDataVisCert == isDataVisCert)&&(identical(other.isBackEndCert, isBackEndCert) || other.isBackEndCert == isBackEndCert)&&(identical(other.isFullStackCert, isFullStackCert) || other.isFullStackCert == isFullStackCert)&&(identical(other.isRespWebDesignCert, isRespWebDesignCert) || other.isRespWebDesignCert == isRespWebDesignCert)&&(identical(other.is2018DataVisCert, is2018DataVisCert) || other.is2018DataVisCert == is2018DataVisCert)&&(identical(other.isFrontEndLibsCert, isFrontEndLibsCert) || other.isFrontEndLibsCert == isFrontEndLibsCert)&&(identical(other.isJsAlgoDataStructCert, isJsAlgoDataStructCert) || other.isJsAlgoDataStructCert == isJsAlgoDataStructCert)&&(identical(other.isApisMicroservicesCert, isApisMicroservicesCert) || other.isApisMicroservicesCert == isApisMicroservicesCert)&&(identical(other.isInfosecQaCert, isInfosecQaCert) || other.isInfosecQaCert == isInfosecQaCert)&&(identical(other.isQaCertV7, isQaCertV7) || other.isQaCertV7 == isQaCertV7)&&(identical(other.isInfosecCertV7, isInfosecCertV7) || other.isInfosecCertV7 == isInfosecCertV7)&&(identical(other.is2018FullStackCert, is2018FullStackCert) || other.is2018FullStackCert == is2018FullStackCert)&&(identical(other.isSciCompPyCertV7, isSciCompPyCertV7) || other.isSciCompPyCertV7 == isSciCompPyCertV7)&&(identical(other.isDataAnalysisPyCertV7, isDataAnalysisPyCertV7) || other.isDataAnalysisPyCertV7 == isDataAnalysisPyCertV7)&&(identical(other.isMachineLearningPyCertV7, isMachineLearningPyCertV7) || other.isMachineLearningPyCertV7 == isMachineLearningPyCertV7)&&(identical(other.isRelationalDatabaseCertV8, isRelationalDatabaseCertV8) || other.isRelationalDatabaseCertV8 == isRelationalDatabaseCertV8)&&(identical(other.isCollegeAlgebraPyCertV8, isCollegeAlgebraPyCertV8) || other.isCollegeAlgebraPyCertV8 == isCollegeAlgebraPyCertV8)&&(identical(other.isFoundationalCSharpCertV8, isFoundationalCSharpCertV8) || other.isFoundationalCSharpCertV8 == isFoundationalCSharpCertV8)&&(identical(other.joinDate, joinDate) || other.joinDate == joinDate)&&(identical(other.points, points) || other.points == points)&&const DeepCollectionEquality().equals(other.calendar, calendar)&&const DeepCollectionEquality().equals(other.completedChallenges, completedChallenges)&&const DeepCollectionEquality().equals(other.completedDailyCodingChallenges, completedDailyCodingChallenges)&&const DeepCollectionEquality().equals(other.savedChallenges, savedChallenges)&&const DeepCollectionEquality().equals(other.portfolio, portfolio)&&const DeepCollectionEquality().equals(other.yearsTopContributor, yearsTopContributor)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hashAll([runtimeType,id,email,username,name,picture,currentChallengeId,githubProfile,linkedin,twitter,website,location,about,emailVerified,isEmailVerified,isCheater,isDonating,isHonest,isBanned,isFrontEndCert,isDataVisCert,isBackEndCert,isFullStackCert,isRespWebDesignCert,is2018DataVisCert,isFrontEndLibsCert,isJsAlgoDataStructCert,isApisMicroservicesCert,isInfosecQaCert,isQaCertV7,isInfosecCertV7,is2018FullStackCert,isSciCompPyCertV7,isDataAnalysisPyCertV7,isMachineLearningPyCertV7,isRelationalDatabaseCertV8,isCollegeAlgebraPyCertV8,isFoundationalCSharpCertV8,joinDate,points,const DeepCollectionEquality().hash(calendar),const DeepCollectionEquality().hash(completedChallenges),const DeepCollectionEquality().hash(completedDailyCodingChallenges),const DeepCollectionEquality().hash(savedChallenges),const DeepCollectionEquality().hash(portfolio),const DeepCollectionEquality().hash(yearsTopContributor)]); + +@override +String toString() { + return 'FccUserModel(id: $id, email: $email, username: $username, name: $name, picture: $picture, currentChallengeId: $currentChallengeId, githubProfile: $githubProfile, linkedin: $linkedin, twitter: $twitter, website: $website, location: $location, about: $about, emailVerified: $emailVerified, isEmailVerified: $isEmailVerified, isCheater: $isCheater, isDonating: $isDonating, isHonest: $isHonest, isBanned: $isBanned, isFrontEndCert: $isFrontEndCert, isDataVisCert: $isDataVisCert, isBackEndCert: $isBackEndCert, isFullStackCert: $isFullStackCert, isRespWebDesignCert: $isRespWebDesignCert, is2018DataVisCert: $is2018DataVisCert, isFrontEndLibsCert: $isFrontEndLibsCert, isJsAlgoDataStructCert: $isJsAlgoDataStructCert, isApisMicroservicesCert: $isApisMicroservicesCert, isInfosecQaCert: $isInfosecQaCert, isQaCertV7: $isQaCertV7, isInfosecCertV7: $isInfosecCertV7, is2018FullStackCert: $is2018FullStackCert, isSciCompPyCertV7: $isSciCompPyCertV7, isDataAnalysisPyCertV7: $isDataAnalysisPyCertV7, isMachineLearningPyCertV7: $isMachineLearningPyCertV7, isRelationalDatabaseCertV8: $isRelationalDatabaseCertV8, isCollegeAlgebraPyCertV8: $isCollegeAlgebraPyCertV8, isFoundationalCSharpCertV8: $isFoundationalCSharpCertV8, joinDate: $joinDate, points: $points, calendar: $calendar, completedChallenges: $completedChallenges, completedDailyCodingChallenges: $completedDailyCodingChallenges, savedChallenges: $savedChallenges, portfolio: $portfolio, yearsTopContributor: $yearsTopContributor)'; +} + + +} + +/// @nodoc +abstract mixin class $FccUserModelCopyWith<$Res> { + factory $FccUserModelCopyWith(FccUserModel value, $Res Function(FccUserModel) _then) = _$FccUserModelCopyWithImpl; +@useResult +$Res call({ + String id, String email, String username, String name, String picture, String? currentChallengeId, String? githubProfile, String? linkedin, String? twitter, String? website, String? location, String? about, bool emailVerified, bool isEmailVerified, bool isCheater, bool isDonating, bool isHonest, bool isBanned, bool isFrontEndCert, bool isDataVisCert, bool isBackEndCert, bool isFullStackCert, bool isRespWebDesignCert, bool is2018DataVisCert, bool isFrontEndLibsCert, bool isJsAlgoDataStructCert, bool isApisMicroservicesCert, bool isInfosecQaCert, bool isQaCertV7, bool isInfosecCertV7, bool is2018FullStackCert, bool isSciCompPyCertV7, bool isDataAnalysisPyCertV7, bool isMachineLearningPyCertV7, bool isRelationalDatabaseCertV8, bool isCollegeAlgebraPyCertV8, bool isFoundationalCSharpCertV8, DateTime joinDate, int points, Map calendar, List completedChallenges, List completedDailyCodingChallenges, List savedChallenges, List portfolio,@JsonKey(fromJson: _yearsToStrings) List yearsTopContributor +}); + + + + +} +/// @nodoc +class _$FccUserModelCopyWithImpl<$Res> + implements $FccUserModelCopyWith<$Res> { + _$FccUserModelCopyWithImpl(this._self, this._then); + + final FccUserModel _self; + final $Res Function(FccUserModel) _then; + +/// Create a copy of FccUserModel +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? email = null,Object? username = null,Object? name = null,Object? picture = null,Object? currentChallengeId = freezed,Object? githubProfile = freezed,Object? linkedin = freezed,Object? twitter = freezed,Object? website = freezed,Object? location = freezed,Object? about = freezed,Object? emailVerified = null,Object? isEmailVerified = null,Object? isCheater = null,Object? isDonating = null,Object? isHonest = null,Object? isBanned = null,Object? isFrontEndCert = null,Object? isDataVisCert = null,Object? isBackEndCert = null,Object? isFullStackCert = null,Object? isRespWebDesignCert = null,Object? is2018DataVisCert = null,Object? isFrontEndLibsCert = null,Object? isJsAlgoDataStructCert = null,Object? isApisMicroservicesCert = null,Object? isInfosecQaCert = null,Object? isQaCertV7 = null,Object? isInfosecCertV7 = null,Object? is2018FullStackCert = null,Object? isSciCompPyCertV7 = null,Object? isDataAnalysisPyCertV7 = null,Object? isMachineLearningPyCertV7 = null,Object? isRelationalDatabaseCertV8 = null,Object? isCollegeAlgebraPyCertV8 = null,Object? isFoundationalCSharpCertV8 = null,Object? joinDate = null,Object? points = null,Object? calendar = null,Object? completedChallenges = null,Object? completedDailyCodingChallenges = null,Object? savedChallenges = null,Object? portfolio = null,Object? yearsTopContributor = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,username: null == username ? _self.username : username // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,picture: null == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable +as String,currentChallengeId: freezed == currentChallengeId ? _self.currentChallengeId : currentChallengeId // ignore: cast_nullable_to_non_nullable +as String?,githubProfile: freezed == githubProfile ? _self.githubProfile : githubProfile // ignore: cast_nullable_to_non_nullable +as String?,linkedin: freezed == linkedin ? _self.linkedin : linkedin // ignore: cast_nullable_to_non_nullable +as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable +as String?,website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable +as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable +as String?,about: freezed == about ? _self.about : about // ignore: cast_nullable_to_non_nullable +as String?,emailVerified: null == emailVerified ? _self.emailVerified : emailVerified // ignore: cast_nullable_to_non_nullable +as bool,isEmailVerified: null == isEmailVerified ? _self.isEmailVerified : isEmailVerified // ignore: cast_nullable_to_non_nullable +as bool,isCheater: null == isCheater ? _self.isCheater : isCheater // ignore: cast_nullable_to_non_nullable +as bool,isDonating: null == isDonating ? _self.isDonating : isDonating // ignore: cast_nullable_to_non_nullable +as bool,isHonest: null == isHonest ? _self.isHonest : isHonest // ignore: cast_nullable_to_non_nullable +as bool,isBanned: null == isBanned ? _self.isBanned : isBanned // ignore: cast_nullable_to_non_nullable +as bool,isFrontEndCert: null == isFrontEndCert ? _self.isFrontEndCert : isFrontEndCert // ignore: cast_nullable_to_non_nullable +as bool,isDataVisCert: null == isDataVisCert ? _self.isDataVisCert : isDataVisCert // ignore: cast_nullable_to_non_nullable +as bool,isBackEndCert: null == isBackEndCert ? _self.isBackEndCert : isBackEndCert // ignore: cast_nullable_to_non_nullable +as bool,isFullStackCert: null == isFullStackCert ? _self.isFullStackCert : isFullStackCert // ignore: cast_nullable_to_non_nullable +as bool,isRespWebDesignCert: null == isRespWebDesignCert ? _self.isRespWebDesignCert : isRespWebDesignCert // ignore: cast_nullable_to_non_nullable +as bool,is2018DataVisCert: null == is2018DataVisCert ? _self.is2018DataVisCert : is2018DataVisCert // ignore: cast_nullable_to_non_nullable +as bool,isFrontEndLibsCert: null == isFrontEndLibsCert ? _self.isFrontEndLibsCert : isFrontEndLibsCert // ignore: cast_nullable_to_non_nullable +as bool,isJsAlgoDataStructCert: null == isJsAlgoDataStructCert ? _self.isJsAlgoDataStructCert : isJsAlgoDataStructCert // ignore: cast_nullable_to_non_nullable +as bool,isApisMicroservicesCert: null == isApisMicroservicesCert ? _self.isApisMicroservicesCert : isApisMicroservicesCert // ignore: cast_nullable_to_non_nullable +as bool,isInfosecQaCert: null == isInfosecQaCert ? _self.isInfosecQaCert : isInfosecQaCert // ignore: cast_nullable_to_non_nullable +as bool,isQaCertV7: null == isQaCertV7 ? _self.isQaCertV7 : isQaCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isInfosecCertV7: null == isInfosecCertV7 ? _self.isInfosecCertV7 : isInfosecCertV7 // ignore: cast_nullable_to_non_nullable +as bool,is2018FullStackCert: null == is2018FullStackCert ? _self.is2018FullStackCert : is2018FullStackCert // ignore: cast_nullable_to_non_nullable +as bool,isSciCompPyCertV7: null == isSciCompPyCertV7 ? _self.isSciCompPyCertV7 : isSciCompPyCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isDataAnalysisPyCertV7: null == isDataAnalysisPyCertV7 ? _self.isDataAnalysisPyCertV7 : isDataAnalysisPyCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isMachineLearningPyCertV7: null == isMachineLearningPyCertV7 ? _self.isMachineLearningPyCertV7 : isMachineLearningPyCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isRelationalDatabaseCertV8: null == isRelationalDatabaseCertV8 ? _self.isRelationalDatabaseCertV8 : isRelationalDatabaseCertV8 // ignore: cast_nullable_to_non_nullable +as bool,isCollegeAlgebraPyCertV8: null == isCollegeAlgebraPyCertV8 ? _self.isCollegeAlgebraPyCertV8 : isCollegeAlgebraPyCertV8 // ignore: cast_nullable_to_non_nullable +as bool,isFoundationalCSharpCertV8: null == isFoundationalCSharpCertV8 ? _self.isFoundationalCSharpCertV8 : isFoundationalCSharpCertV8 // ignore: cast_nullable_to_non_nullable +as bool,joinDate: null == joinDate ? _self.joinDate : joinDate // ignore: cast_nullable_to_non_nullable +as DateTime,points: null == points ? _self.points : points // ignore: cast_nullable_to_non_nullable +as int,calendar: null == calendar ? _self.calendar : calendar // ignore: cast_nullable_to_non_nullable +as Map,completedChallenges: null == completedChallenges ? _self.completedChallenges : completedChallenges // ignore: cast_nullable_to_non_nullable +as List,completedDailyCodingChallenges: null == completedDailyCodingChallenges ? _self.completedDailyCodingChallenges : completedDailyCodingChallenges // ignore: cast_nullable_to_non_nullable +as List,savedChallenges: null == savedChallenges ? _self.savedChallenges : savedChallenges // ignore: cast_nullable_to_non_nullable +as List,portfolio: null == portfolio ? _self.portfolio : portfolio // ignore: cast_nullable_to_non_nullable +as List,yearsTopContributor: null == yearsTopContributor ? _self.yearsTopContributor : yearsTopContributor // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [FccUserModel]. +extension FccUserModelPatterns on FccUserModel { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _FccUserModel value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _FccUserModel() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _FccUserModel value) $default,){ +final _that = this; +switch (_that) { +case _FccUserModel(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _FccUserModel value)? $default,){ +final _that = this; +switch (_that) { +case _FccUserModel() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String email, String username, String name, String picture, String? currentChallengeId, String? githubProfile, String? linkedin, String? twitter, String? website, String? location, String? about, bool emailVerified, bool isEmailVerified, bool isCheater, bool isDonating, bool isHonest, bool isBanned, bool isFrontEndCert, bool isDataVisCert, bool isBackEndCert, bool isFullStackCert, bool isRespWebDesignCert, bool is2018DataVisCert, bool isFrontEndLibsCert, bool isJsAlgoDataStructCert, bool isApisMicroservicesCert, bool isInfosecQaCert, bool isQaCertV7, bool isInfosecCertV7, bool is2018FullStackCert, bool isSciCompPyCertV7, bool isDataAnalysisPyCertV7, bool isMachineLearningPyCertV7, bool isRelationalDatabaseCertV8, bool isCollegeAlgebraPyCertV8, bool isFoundationalCSharpCertV8, DateTime joinDate, int points, Map calendar, List completedChallenges, List completedDailyCodingChallenges, List savedChallenges, List portfolio, @JsonKey(fromJson: _yearsToStrings) List yearsTopContributor)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _FccUserModel() when $default != null: +return $default(_that.id,_that.email,_that.username,_that.name,_that.picture,_that.currentChallengeId,_that.githubProfile,_that.linkedin,_that.twitter,_that.website,_that.location,_that.about,_that.emailVerified,_that.isEmailVerified,_that.isCheater,_that.isDonating,_that.isHonest,_that.isBanned,_that.isFrontEndCert,_that.isDataVisCert,_that.isBackEndCert,_that.isFullStackCert,_that.isRespWebDesignCert,_that.is2018DataVisCert,_that.isFrontEndLibsCert,_that.isJsAlgoDataStructCert,_that.isApisMicroservicesCert,_that.isInfosecQaCert,_that.isQaCertV7,_that.isInfosecCertV7,_that.is2018FullStackCert,_that.isSciCompPyCertV7,_that.isDataAnalysisPyCertV7,_that.isMachineLearningPyCertV7,_that.isRelationalDatabaseCertV8,_that.isCollegeAlgebraPyCertV8,_that.isFoundationalCSharpCertV8,_that.joinDate,_that.points,_that.calendar,_that.completedChallenges,_that.completedDailyCodingChallenges,_that.savedChallenges,_that.portfolio,_that.yearsTopContributor);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String email, String username, String name, String picture, String? currentChallengeId, String? githubProfile, String? linkedin, String? twitter, String? website, String? location, String? about, bool emailVerified, bool isEmailVerified, bool isCheater, bool isDonating, bool isHonest, bool isBanned, bool isFrontEndCert, bool isDataVisCert, bool isBackEndCert, bool isFullStackCert, bool isRespWebDesignCert, bool is2018DataVisCert, bool isFrontEndLibsCert, bool isJsAlgoDataStructCert, bool isApisMicroservicesCert, bool isInfosecQaCert, bool isQaCertV7, bool isInfosecCertV7, bool is2018FullStackCert, bool isSciCompPyCertV7, bool isDataAnalysisPyCertV7, bool isMachineLearningPyCertV7, bool isRelationalDatabaseCertV8, bool isCollegeAlgebraPyCertV8, bool isFoundationalCSharpCertV8, DateTime joinDate, int points, Map calendar, List completedChallenges, List completedDailyCodingChallenges, List savedChallenges, List portfolio, @JsonKey(fromJson: _yearsToStrings) List yearsTopContributor) $default,) {final _that = this; +switch (_that) { +case _FccUserModel(): +return $default(_that.id,_that.email,_that.username,_that.name,_that.picture,_that.currentChallengeId,_that.githubProfile,_that.linkedin,_that.twitter,_that.website,_that.location,_that.about,_that.emailVerified,_that.isEmailVerified,_that.isCheater,_that.isDonating,_that.isHonest,_that.isBanned,_that.isFrontEndCert,_that.isDataVisCert,_that.isBackEndCert,_that.isFullStackCert,_that.isRespWebDesignCert,_that.is2018DataVisCert,_that.isFrontEndLibsCert,_that.isJsAlgoDataStructCert,_that.isApisMicroservicesCert,_that.isInfosecQaCert,_that.isQaCertV7,_that.isInfosecCertV7,_that.is2018FullStackCert,_that.isSciCompPyCertV7,_that.isDataAnalysisPyCertV7,_that.isMachineLearningPyCertV7,_that.isRelationalDatabaseCertV8,_that.isCollegeAlgebraPyCertV8,_that.isFoundationalCSharpCertV8,_that.joinDate,_that.points,_that.calendar,_that.completedChallenges,_that.completedDailyCodingChallenges,_that.savedChallenges,_that.portfolio,_that.yearsTopContributor);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String email, String username, String name, String picture, String? currentChallengeId, String? githubProfile, String? linkedin, String? twitter, String? website, String? location, String? about, bool emailVerified, bool isEmailVerified, bool isCheater, bool isDonating, bool isHonest, bool isBanned, bool isFrontEndCert, bool isDataVisCert, bool isBackEndCert, bool isFullStackCert, bool isRespWebDesignCert, bool is2018DataVisCert, bool isFrontEndLibsCert, bool isJsAlgoDataStructCert, bool isApisMicroservicesCert, bool isInfosecQaCert, bool isQaCertV7, bool isInfosecCertV7, bool is2018FullStackCert, bool isSciCompPyCertV7, bool isDataAnalysisPyCertV7, bool isMachineLearningPyCertV7, bool isRelationalDatabaseCertV8, bool isCollegeAlgebraPyCertV8, bool isFoundationalCSharpCertV8, DateTime joinDate, int points, Map calendar, List completedChallenges, List completedDailyCodingChallenges, List savedChallenges, List portfolio, @JsonKey(fromJson: _yearsToStrings) List yearsTopContributor)? $default,) {final _that = this; +switch (_that) { +case _FccUserModel() when $default != null: +return $default(_that.id,_that.email,_that.username,_that.name,_that.picture,_that.currentChallengeId,_that.githubProfile,_that.linkedin,_that.twitter,_that.website,_that.location,_that.about,_that.emailVerified,_that.isEmailVerified,_that.isCheater,_that.isDonating,_that.isHonest,_that.isBanned,_that.isFrontEndCert,_that.isDataVisCert,_that.isBackEndCert,_that.isFullStackCert,_that.isRespWebDesignCert,_that.is2018DataVisCert,_that.isFrontEndLibsCert,_that.isJsAlgoDataStructCert,_that.isApisMicroservicesCert,_that.isInfosecQaCert,_that.isQaCertV7,_that.isInfosecCertV7,_that.is2018FullStackCert,_that.isSciCompPyCertV7,_that.isDataAnalysisPyCertV7,_that.isMachineLearningPyCertV7,_that.isRelationalDatabaseCertV8,_that.isCollegeAlgebraPyCertV8,_that.isFoundationalCSharpCertV8,_that.joinDate,_that.points,_that.calendar,_that.completedChallenges,_that.completedDailyCodingChallenges,_that.savedChallenges,_that.portfolio,_that.yearsTopContributor);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _FccUserModel extends FccUserModel { + const _FccUserModel({required this.id, required this.email, required this.username, this.name = '', this.picture = '', this.currentChallengeId, this.githubProfile, this.linkedin, this.twitter, this.website, this.location, this.about, this.emailVerified = false, this.isEmailVerified = false, this.isCheater = false, this.isDonating = false, this.isHonest = false, this.isBanned = false, this.isFrontEndCert = false, this.isDataVisCert = false, this.isBackEndCert = false, this.isFullStackCert = false, this.isRespWebDesignCert = false, this.is2018DataVisCert = false, this.isFrontEndLibsCert = false, this.isJsAlgoDataStructCert = false, this.isApisMicroservicesCert = false, this.isInfosecQaCert = false, this.isQaCertV7 = false, this.isInfosecCertV7 = false, this.is2018FullStackCert = false, this.isSciCompPyCertV7 = false, this.isDataAnalysisPyCertV7 = false, this.isMachineLearningPyCertV7 = false, this.isRelationalDatabaseCertV8 = false, this.isCollegeAlgebraPyCertV8 = false, this.isFoundationalCSharpCertV8 = false, required this.joinDate, this.points = 0, final Map calendar = const {}, final List completedChallenges = const [], final List completedDailyCodingChallenges = const [], final List savedChallenges = const [], final List portfolio = const [], @JsonKey(fromJson: _yearsToStrings) final List yearsTopContributor = const []}): _calendar = calendar,_completedChallenges = completedChallenges,_completedDailyCodingChallenges = completedDailyCodingChallenges,_savedChallenges = savedChallenges,_portfolio = portfolio,_yearsTopContributor = yearsTopContributor,super._(); + factory _FccUserModel.fromJson(Map json) => _$FccUserModelFromJson(json); + +@override final String id; +@override final String email; +@override final String username; +@override@JsonKey() final String name; +@override@JsonKey() final String picture; +@override final String? currentChallengeId; +@override final String? githubProfile; +@override final String? linkedin; +@override final String? twitter; +@override final String? website; +@override final String? location; +@override final String? about; +@override@JsonKey() final bool emailVerified; +@override@JsonKey() final bool isEmailVerified; +@override@JsonKey() final bool isCheater; +@override@JsonKey() final bool isDonating; +@override@JsonKey() final bool isHonest; +@override@JsonKey() final bool isBanned; +@override@JsonKey() final bool isFrontEndCert; +@override@JsonKey() final bool isDataVisCert; +@override@JsonKey() final bool isBackEndCert; +@override@JsonKey() final bool isFullStackCert; +@override@JsonKey() final bool isRespWebDesignCert; +@override@JsonKey() final bool is2018DataVisCert; +@override@JsonKey() final bool isFrontEndLibsCert; +@override@JsonKey() final bool isJsAlgoDataStructCert; +@override@JsonKey() final bool isApisMicroservicesCert; +@override@JsonKey() final bool isInfosecQaCert; +@override@JsonKey() final bool isQaCertV7; +@override@JsonKey() final bool isInfosecCertV7; +@override@JsonKey() final bool is2018FullStackCert; +@override@JsonKey() final bool isSciCompPyCertV7; +@override@JsonKey() final bool isDataAnalysisPyCertV7; +@override@JsonKey() final bool isMachineLearningPyCertV7; +@override@JsonKey() final bool isRelationalDatabaseCertV8; +@override@JsonKey() final bool isCollegeAlgebraPyCertV8; +@override@JsonKey() final bool isFoundationalCSharpCertV8; +@override final DateTime joinDate; +@override@JsonKey() final int points; + final Map _calendar; +@override@JsonKey() Map get calendar { + if (_calendar is EqualUnmodifiableMapView) return _calendar; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_calendar); +} + + final List _completedChallenges; +@override@JsonKey() List get completedChallenges { + if (_completedChallenges is EqualUnmodifiableListView) return _completedChallenges; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_completedChallenges); +} + + final List _completedDailyCodingChallenges; +@override@JsonKey() List get completedDailyCodingChallenges { + if (_completedDailyCodingChallenges is EqualUnmodifiableListView) return _completedDailyCodingChallenges; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_completedDailyCodingChallenges); +} + + final List _savedChallenges; +@override@JsonKey() List get savedChallenges { + if (_savedChallenges is EqualUnmodifiableListView) return _savedChallenges; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_savedChallenges); +} + + final List _portfolio; +@override@JsonKey() List get portfolio { + if (_portfolio is EqualUnmodifiableListView) return _portfolio; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_portfolio); +} + + final List _yearsTopContributor; +@override@JsonKey(fromJson: _yearsToStrings) List get yearsTopContributor { + if (_yearsTopContributor is EqualUnmodifiableListView) return _yearsTopContributor; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_yearsTopContributor); +} + + +/// Create a copy of FccUserModel +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$FccUserModelCopyWith<_FccUserModel> get copyWith => __$FccUserModelCopyWithImpl<_FccUserModel>(this, _$identity); + +@override +Map toJson() { + return _$FccUserModelToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _FccUserModel&&(identical(other.id, id) || other.id == id)&&(identical(other.email, email) || other.email == email)&&(identical(other.username, username) || other.username == username)&&(identical(other.name, name) || other.name == name)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.currentChallengeId, currentChallengeId) || other.currentChallengeId == currentChallengeId)&&(identical(other.githubProfile, githubProfile) || other.githubProfile == githubProfile)&&(identical(other.linkedin, linkedin) || other.linkedin == linkedin)&&(identical(other.twitter, twitter) || other.twitter == twitter)&&(identical(other.website, website) || other.website == website)&&(identical(other.location, location) || other.location == location)&&(identical(other.about, about) || other.about == about)&&(identical(other.emailVerified, emailVerified) || other.emailVerified == emailVerified)&&(identical(other.isEmailVerified, isEmailVerified) || other.isEmailVerified == isEmailVerified)&&(identical(other.isCheater, isCheater) || other.isCheater == isCheater)&&(identical(other.isDonating, isDonating) || other.isDonating == isDonating)&&(identical(other.isHonest, isHonest) || other.isHonest == isHonest)&&(identical(other.isBanned, isBanned) || other.isBanned == isBanned)&&(identical(other.isFrontEndCert, isFrontEndCert) || other.isFrontEndCert == isFrontEndCert)&&(identical(other.isDataVisCert, isDataVisCert) || other.isDataVisCert == isDataVisCert)&&(identical(other.isBackEndCert, isBackEndCert) || other.isBackEndCert == isBackEndCert)&&(identical(other.isFullStackCert, isFullStackCert) || other.isFullStackCert == isFullStackCert)&&(identical(other.isRespWebDesignCert, isRespWebDesignCert) || other.isRespWebDesignCert == isRespWebDesignCert)&&(identical(other.is2018DataVisCert, is2018DataVisCert) || other.is2018DataVisCert == is2018DataVisCert)&&(identical(other.isFrontEndLibsCert, isFrontEndLibsCert) || other.isFrontEndLibsCert == isFrontEndLibsCert)&&(identical(other.isJsAlgoDataStructCert, isJsAlgoDataStructCert) || other.isJsAlgoDataStructCert == isJsAlgoDataStructCert)&&(identical(other.isApisMicroservicesCert, isApisMicroservicesCert) || other.isApisMicroservicesCert == isApisMicroservicesCert)&&(identical(other.isInfosecQaCert, isInfosecQaCert) || other.isInfosecQaCert == isInfosecQaCert)&&(identical(other.isQaCertV7, isQaCertV7) || other.isQaCertV7 == isQaCertV7)&&(identical(other.isInfosecCertV7, isInfosecCertV7) || other.isInfosecCertV7 == isInfosecCertV7)&&(identical(other.is2018FullStackCert, is2018FullStackCert) || other.is2018FullStackCert == is2018FullStackCert)&&(identical(other.isSciCompPyCertV7, isSciCompPyCertV7) || other.isSciCompPyCertV7 == isSciCompPyCertV7)&&(identical(other.isDataAnalysisPyCertV7, isDataAnalysisPyCertV7) || other.isDataAnalysisPyCertV7 == isDataAnalysisPyCertV7)&&(identical(other.isMachineLearningPyCertV7, isMachineLearningPyCertV7) || other.isMachineLearningPyCertV7 == isMachineLearningPyCertV7)&&(identical(other.isRelationalDatabaseCertV8, isRelationalDatabaseCertV8) || other.isRelationalDatabaseCertV8 == isRelationalDatabaseCertV8)&&(identical(other.isCollegeAlgebraPyCertV8, isCollegeAlgebraPyCertV8) || other.isCollegeAlgebraPyCertV8 == isCollegeAlgebraPyCertV8)&&(identical(other.isFoundationalCSharpCertV8, isFoundationalCSharpCertV8) || other.isFoundationalCSharpCertV8 == isFoundationalCSharpCertV8)&&(identical(other.joinDate, joinDate) || other.joinDate == joinDate)&&(identical(other.points, points) || other.points == points)&&const DeepCollectionEquality().equals(other._calendar, _calendar)&&const DeepCollectionEquality().equals(other._completedChallenges, _completedChallenges)&&const DeepCollectionEquality().equals(other._completedDailyCodingChallenges, _completedDailyCodingChallenges)&&const DeepCollectionEquality().equals(other._savedChallenges, _savedChallenges)&&const DeepCollectionEquality().equals(other._portfolio, _portfolio)&&const DeepCollectionEquality().equals(other._yearsTopContributor, _yearsTopContributor)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hashAll([runtimeType,id,email,username,name,picture,currentChallengeId,githubProfile,linkedin,twitter,website,location,about,emailVerified,isEmailVerified,isCheater,isDonating,isHonest,isBanned,isFrontEndCert,isDataVisCert,isBackEndCert,isFullStackCert,isRespWebDesignCert,is2018DataVisCert,isFrontEndLibsCert,isJsAlgoDataStructCert,isApisMicroservicesCert,isInfosecQaCert,isQaCertV7,isInfosecCertV7,is2018FullStackCert,isSciCompPyCertV7,isDataAnalysisPyCertV7,isMachineLearningPyCertV7,isRelationalDatabaseCertV8,isCollegeAlgebraPyCertV8,isFoundationalCSharpCertV8,joinDate,points,const DeepCollectionEquality().hash(_calendar),const DeepCollectionEquality().hash(_completedChallenges),const DeepCollectionEquality().hash(_completedDailyCodingChallenges),const DeepCollectionEquality().hash(_savedChallenges),const DeepCollectionEquality().hash(_portfolio),const DeepCollectionEquality().hash(_yearsTopContributor)]); + +@override +String toString() { + return 'FccUserModel(id: $id, email: $email, username: $username, name: $name, picture: $picture, currentChallengeId: $currentChallengeId, githubProfile: $githubProfile, linkedin: $linkedin, twitter: $twitter, website: $website, location: $location, about: $about, emailVerified: $emailVerified, isEmailVerified: $isEmailVerified, isCheater: $isCheater, isDonating: $isDonating, isHonest: $isHonest, isBanned: $isBanned, isFrontEndCert: $isFrontEndCert, isDataVisCert: $isDataVisCert, isBackEndCert: $isBackEndCert, isFullStackCert: $isFullStackCert, isRespWebDesignCert: $isRespWebDesignCert, is2018DataVisCert: $is2018DataVisCert, isFrontEndLibsCert: $isFrontEndLibsCert, isJsAlgoDataStructCert: $isJsAlgoDataStructCert, isApisMicroservicesCert: $isApisMicroservicesCert, isInfosecQaCert: $isInfosecQaCert, isQaCertV7: $isQaCertV7, isInfosecCertV7: $isInfosecCertV7, is2018FullStackCert: $is2018FullStackCert, isSciCompPyCertV7: $isSciCompPyCertV7, isDataAnalysisPyCertV7: $isDataAnalysisPyCertV7, isMachineLearningPyCertV7: $isMachineLearningPyCertV7, isRelationalDatabaseCertV8: $isRelationalDatabaseCertV8, isCollegeAlgebraPyCertV8: $isCollegeAlgebraPyCertV8, isFoundationalCSharpCertV8: $isFoundationalCSharpCertV8, joinDate: $joinDate, points: $points, calendar: $calendar, completedChallenges: $completedChallenges, completedDailyCodingChallenges: $completedDailyCodingChallenges, savedChallenges: $savedChallenges, portfolio: $portfolio, yearsTopContributor: $yearsTopContributor)'; +} + + +} + +/// @nodoc +abstract mixin class _$FccUserModelCopyWith<$Res> implements $FccUserModelCopyWith<$Res> { + factory _$FccUserModelCopyWith(_FccUserModel value, $Res Function(_FccUserModel) _then) = __$FccUserModelCopyWithImpl; +@override @useResult +$Res call({ + String id, String email, String username, String name, String picture, String? currentChallengeId, String? githubProfile, String? linkedin, String? twitter, String? website, String? location, String? about, bool emailVerified, bool isEmailVerified, bool isCheater, bool isDonating, bool isHonest, bool isBanned, bool isFrontEndCert, bool isDataVisCert, bool isBackEndCert, bool isFullStackCert, bool isRespWebDesignCert, bool is2018DataVisCert, bool isFrontEndLibsCert, bool isJsAlgoDataStructCert, bool isApisMicroservicesCert, bool isInfosecQaCert, bool isQaCertV7, bool isInfosecCertV7, bool is2018FullStackCert, bool isSciCompPyCertV7, bool isDataAnalysisPyCertV7, bool isMachineLearningPyCertV7, bool isRelationalDatabaseCertV8, bool isCollegeAlgebraPyCertV8, bool isFoundationalCSharpCertV8, DateTime joinDate, int points, Map calendar, List completedChallenges, List completedDailyCodingChallenges, List savedChallenges, List portfolio,@JsonKey(fromJson: _yearsToStrings) List yearsTopContributor +}); + + + + +} +/// @nodoc +class __$FccUserModelCopyWithImpl<$Res> + implements _$FccUserModelCopyWith<$Res> { + __$FccUserModelCopyWithImpl(this._self, this._then); + + final _FccUserModel _self; + final $Res Function(_FccUserModel) _then; + +/// Create a copy of FccUserModel +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? email = null,Object? username = null,Object? name = null,Object? picture = null,Object? currentChallengeId = freezed,Object? githubProfile = freezed,Object? linkedin = freezed,Object? twitter = freezed,Object? website = freezed,Object? location = freezed,Object? about = freezed,Object? emailVerified = null,Object? isEmailVerified = null,Object? isCheater = null,Object? isDonating = null,Object? isHonest = null,Object? isBanned = null,Object? isFrontEndCert = null,Object? isDataVisCert = null,Object? isBackEndCert = null,Object? isFullStackCert = null,Object? isRespWebDesignCert = null,Object? is2018DataVisCert = null,Object? isFrontEndLibsCert = null,Object? isJsAlgoDataStructCert = null,Object? isApisMicroservicesCert = null,Object? isInfosecQaCert = null,Object? isQaCertV7 = null,Object? isInfosecCertV7 = null,Object? is2018FullStackCert = null,Object? isSciCompPyCertV7 = null,Object? isDataAnalysisPyCertV7 = null,Object? isMachineLearningPyCertV7 = null,Object? isRelationalDatabaseCertV8 = null,Object? isCollegeAlgebraPyCertV8 = null,Object? isFoundationalCSharpCertV8 = null,Object? joinDate = null,Object? points = null,Object? calendar = null,Object? completedChallenges = null,Object? completedDailyCodingChallenges = null,Object? savedChallenges = null,Object? portfolio = null,Object? yearsTopContributor = null,}) { + return _then(_FccUserModel( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,email: null == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String,username: null == username ? _self.username : username // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,picture: null == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable +as String,currentChallengeId: freezed == currentChallengeId ? _self.currentChallengeId : currentChallengeId // ignore: cast_nullable_to_non_nullable +as String?,githubProfile: freezed == githubProfile ? _self.githubProfile : githubProfile // ignore: cast_nullable_to_non_nullable +as String?,linkedin: freezed == linkedin ? _self.linkedin : linkedin // ignore: cast_nullable_to_non_nullable +as String?,twitter: freezed == twitter ? _self.twitter : twitter // ignore: cast_nullable_to_non_nullable +as String?,website: freezed == website ? _self.website : website // ignore: cast_nullable_to_non_nullable +as String?,location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable +as String?,about: freezed == about ? _self.about : about // ignore: cast_nullable_to_non_nullable +as String?,emailVerified: null == emailVerified ? _self.emailVerified : emailVerified // ignore: cast_nullable_to_non_nullable +as bool,isEmailVerified: null == isEmailVerified ? _self.isEmailVerified : isEmailVerified // ignore: cast_nullable_to_non_nullable +as bool,isCheater: null == isCheater ? _self.isCheater : isCheater // ignore: cast_nullable_to_non_nullable +as bool,isDonating: null == isDonating ? _self.isDonating : isDonating // ignore: cast_nullable_to_non_nullable +as bool,isHonest: null == isHonest ? _self.isHonest : isHonest // ignore: cast_nullable_to_non_nullable +as bool,isBanned: null == isBanned ? _self.isBanned : isBanned // ignore: cast_nullable_to_non_nullable +as bool,isFrontEndCert: null == isFrontEndCert ? _self.isFrontEndCert : isFrontEndCert // ignore: cast_nullable_to_non_nullable +as bool,isDataVisCert: null == isDataVisCert ? _self.isDataVisCert : isDataVisCert // ignore: cast_nullable_to_non_nullable +as bool,isBackEndCert: null == isBackEndCert ? _self.isBackEndCert : isBackEndCert // ignore: cast_nullable_to_non_nullable +as bool,isFullStackCert: null == isFullStackCert ? _self.isFullStackCert : isFullStackCert // ignore: cast_nullable_to_non_nullable +as bool,isRespWebDesignCert: null == isRespWebDesignCert ? _self.isRespWebDesignCert : isRespWebDesignCert // ignore: cast_nullable_to_non_nullable +as bool,is2018DataVisCert: null == is2018DataVisCert ? _self.is2018DataVisCert : is2018DataVisCert // ignore: cast_nullable_to_non_nullable +as bool,isFrontEndLibsCert: null == isFrontEndLibsCert ? _self.isFrontEndLibsCert : isFrontEndLibsCert // ignore: cast_nullable_to_non_nullable +as bool,isJsAlgoDataStructCert: null == isJsAlgoDataStructCert ? _self.isJsAlgoDataStructCert : isJsAlgoDataStructCert // ignore: cast_nullable_to_non_nullable +as bool,isApisMicroservicesCert: null == isApisMicroservicesCert ? _self.isApisMicroservicesCert : isApisMicroservicesCert // ignore: cast_nullable_to_non_nullable +as bool,isInfosecQaCert: null == isInfosecQaCert ? _self.isInfosecQaCert : isInfosecQaCert // ignore: cast_nullable_to_non_nullable +as bool,isQaCertV7: null == isQaCertV7 ? _self.isQaCertV7 : isQaCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isInfosecCertV7: null == isInfosecCertV7 ? _self.isInfosecCertV7 : isInfosecCertV7 // ignore: cast_nullable_to_non_nullable +as bool,is2018FullStackCert: null == is2018FullStackCert ? _self.is2018FullStackCert : is2018FullStackCert // ignore: cast_nullable_to_non_nullable +as bool,isSciCompPyCertV7: null == isSciCompPyCertV7 ? _self.isSciCompPyCertV7 : isSciCompPyCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isDataAnalysisPyCertV7: null == isDataAnalysisPyCertV7 ? _self.isDataAnalysisPyCertV7 : isDataAnalysisPyCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isMachineLearningPyCertV7: null == isMachineLearningPyCertV7 ? _self.isMachineLearningPyCertV7 : isMachineLearningPyCertV7 // ignore: cast_nullable_to_non_nullable +as bool,isRelationalDatabaseCertV8: null == isRelationalDatabaseCertV8 ? _self.isRelationalDatabaseCertV8 : isRelationalDatabaseCertV8 // ignore: cast_nullable_to_non_nullable +as bool,isCollegeAlgebraPyCertV8: null == isCollegeAlgebraPyCertV8 ? _self.isCollegeAlgebraPyCertV8 : isCollegeAlgebraPyCertV8 // ignore: cast_nullable_to_non_nullable +as bool,isFoundationalCSharpCertV8: null == isFoundationalCSharpCertV8 ? _self.isFoundationalCSharpCertV8 : isFoundationalCSharpCertV8 // ignore: cast_nullable_to_non_nullable +as bool,joinDate: null == joinDate ? _self.joinDate : joinDate // ignore: cast_nullable_to_non_nullable +as DateTime,points: null == points ? _self.points : points // ignore: cast_nullable_to_non_nullable +as int,calendar: null == calendar ? _self._calendar : calendar // ignore: cast_nullable_to_non_nullable +as Map,completedChallenges: null == completedChallenges ? _self._completedChallenges : completedChallenges // ignore: cast_nullable_to_non_nullable +as List,completedDailyCodingChallenges: null == completedDailyCodingChallenges ? _self._completedDailyCodingChallenges : completedDailyCodingChallenges // ignore: cast_nullable_to_non_nullable +as List,savedChallenges: null == savedChallenges ? _self._savedChallenges : savedChallenges // ignore: cast_nullable_to_non_nullable +as List,portfolio: null == portfolio ? _self._portfolio : portfolio // ignore: cast_nullable_to_non_nullable +as List,yearsTopContributor: null == yearsTopContributor ? _self._yearsTopContributor : yearsTopContributor // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/auth/models/user_model.g.dart b/mobile-app-new/lib/auth/models/user_model.g.dart new file mode 100644 index 000000000..020de1ea8 --- /dev/null +++ b/mobile-app-new/lib/auth/models/user_model.g.dart @@ -0,0 +1,205 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Portfolio _$PortfolioFromJson(Map json) => _Portfolio( + id: json['id'] as String, + title: json['title'] as String?, + url: json['url'] as String?, + image: json['image'] as String?, + description: json['description'] as String?, +); + +Map _$PortfolioToJson(_Portfolio instance) => + { + 'id': instance.id, + 'title': instance.title, + 'url': instance.url, + 'image': instance.image, + 'description': instance.description, + }; + +_CompletedChallenge _$CompletedChallengeFromJson(Map json) => + _CompletedChallenge( + id: json['id'] as String, + solution: json['solution'] as String?, + githubLink: json['githubLink'] as String?, + challengeType: (json['challengeType'] as num?)?.toInt(), + completedDate: _fromEpochMs((json['completedDate'] as num).toInt()), + files: + (json['files'] as List?) + ?.map((e) => ChallengeFile.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$CompletedChallengeToJson(_CompletedChallenge instance) => + { + 'id': instance.id, + 'solution': instance.solution, + 'githubLink': instance.githubLink, + 'challengeType': instance.challengeType, + 'completedDate': _toEpochMs(instance.completedDate), + 'files': instance.files, + }; + +_CompletedDailyChallenge _$CompletedDailyChallengeFromJson( + Map json, +) => _CompletedDailyChallenge( + id: json['id'] as String, + completedDate: _fromEpochMs((json['completedDate'] as num).toInt()), + languages: + (json['languages'] as List?)?.map((e) => e as String).toList() ?? + const [], +); + +Map _$CompletedDailyChallengeToJson( + _CompletedDailyChallenge instance, +) => { + 'id': instance.id, + 'completedDate': _toEpochMs(instance.completedDate), + 'languages': instance.languages, +}; + +_SavedChallenge _$SavedChallengeFromJson(Map json) => + _SavedChallenge( + id: json['id'] as String, + files: + (json['files'] as List?) + ?.map((e) => ChallengeFile.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$SavedChallengeToJson(_SavedChallenge instance) => + {'id': instance.id, 'files': instance.files}; + +_FccUserModel _$FccUserModelFromJson( + Map json, +) => _FccUserModel( + id: json['id'] as String, + email: json['email'] as String, + username: json['username'] as String, + name: json['name'] as String? ?? '', + picture: json['picture'] as String? ?? '', + currentChallengeId: json['currentChallengeId'] as String?, + githubProfile: json['githubProfile'] as String?, + linkedin: json['linkedin'] as String?, + twitter: json['twitter'] as String?, + website: json['website'] as String?, + location: json['location'] as String?, + about: json['about'] as String?, + emailVerified: json['emailVerified'] as bool? ?? false, + isEmailVerified: json['isEmailVerified'] as bool? ?? false, + isCheater: json['isCheater'] as bool? ?? false, + isDonating: json['isDonating'] as bool? ?? false, + isHonest: json['isHonest'] as bool? ?? false, + isBanned: json['isBanned'] as bool? ?? false, + isFrontEndCert: json['isFrontEndCert'] as bool? ?? false, + isDataVisCert: json['isDataVisCert'] as bool? ?? false, + isBackEndCert: json['isBackEndCert'] as bool? ?? false, + isFullStackCert: json['isFullStackCert'] as bool? ?? false, + isRespWebDesignCert: json['isRespWebDesignCert'] as bool? ?? false, + is2018DataVisCert: json['is2018DataVisCert'] as bool? ?? false, + isFrontEndLibsCert: json['isFrontEndLibsCert'] as bool? ?? false, + isJsAlgoDataStructCert: json['isJsAlgoDataStructCert'] as bool? ?? false, + isApisMicroservicesCert: json['isApisMicroservicesCert'] as bool? ?? false, + isInfosecQaCert: json['isInfosecQaCert'] as bool? ?? false, + isQaCertV7: json['isQaCertV7'] as bool? ?? false, + isInfosecCertV7: json['isInfosecCertV7'] as bool? ?? false, + is2018FullStackCert: json['is2018FullStackCert'] as bool? ?? false, + isSciCompPyCertV7: json['isSciCompPyCertV7'] as bool? ?? false, + isDataAnalysisPyCertV7: json['isDataAnalysisPyCertV7'] as bool? ?? false, + isMachineLearningPyCertV7: + json['isMachineLearningPyCertV7'] as bool? ?? false, + isRelationalDatabaseCertV8: + json['isRelationalDatabaseCertV8'] as bool? ?? false, + isCollegeAlgebraPyCertV8: json['isCollegeAlgebraPyCertV8'] as bool? ?? false, + isFoundationalCSharpCertV8: + json['isFoundationalCSharpCertV8'] as bool? ?? false, + joinDate: DateTime.parse(json['joinDate'] as String), + points: (json['points'] as num?)?.toInt() ?? 0, + calendar: + (json['calendar'] as Map?)?.map( + (k, e) => MapEntry(k, (e as num).toInt()), + ) ?? + const {}, + completedChallenges: + (json['completedChallenges'] as List?) + ?.map((e) => CompletedChallenge.fromJson(e as Map)) + .toList() ?? + const [], + completedDailyCodingChallenges: + (json['completedDailyCodingChallenges'] as List?) + ?.map( + (e) => CompletedDailyChallenge.fromJson(e as Map), + ) + .toList() ?? + const [], + savedChallenges: + (json['savedChallenges'] as List?) + ?.map((e) => SavedChallenge.fromJson(e as Map)) + .toList() ?? + const [], + portfolio: + (json['portfolio'] as List?) + ?.map((e) => Portfolio.fromJson(e as Map)) + .toList() ?? + const [], + yearsTopContributor: json['yearsTopContributor'] == null + ? const [] + : _yearsToStrings(json['yearsTopContributor'] as List), +); + +Map _$FccUserModelToJson(_FccUserModel instance) => + { + 'id': instance.id, + 'email': instance.email, + 'username': instance.username, + 'name': instance.name, + 'picture': instance.picture, + 'currentChallengeId': instance.currentChallengeId, + 'githubProfile': instance.githubProfile, + 'linkedin': instance.linkedin, + 'twitter': instance.twitter, + 'website': instance.website, + 'location': instance.location, + 'about': instance.about, + 'emailVerified': instance.emailVerified, + 'isEmailVerified': instance.isEmailVerified, + 'isCheater': instance.isCheater, + 'isDonating': instance.isDonating, + 'isHonest': instance.isHonest, + 'isBanned': instance.isBanned, + 'isFrontEndCert': instance.isFrontEndCert, + 'isDataVisCert': instance.isDataVisCert, + 'isBackEndCert': instance.isBackEndCert, + 'isFullStackCert': instance.isFullStackCert, + 'isRespWebDesignCert': instance.isRespWebDesignCert, + 'is2018DataVisCert': instance.is2018DataVisCert, + 'isFrontEndLibsCert': instance.isFrontEndLibsCert, + 'isJsAlgoDataStructCert': instance.isJsAlgoDataStructCert, + 'isApisMicroservicesCert': instance.isApisMicroservicesCert, + 'isInfosecQaCert': instance.isInfosecQaCert, + 'isQaCertV7': instance.isQaCertV7, + 'isInfosecCertV7': instance.isInfosecCertV7, + 'is2018FullStackCert': instance.is2018FullStackCert, + 'isSciCompPyCertV7': instance.isSciCompPyCertV7, + 'isDataAnalysisPyCertV7': instance.isDataAnalysisPyCertV7, + 'isMachineLearningPyCertV7': instance.isMachineLearningPyCertV7, + 'isRelationalDatabaseCertV8': instance.isRelationalDatabaseCertV8, + 'isCollegeAlgebraPyCertV8': instance.isCollegeAlgebraPyCertV8, + 'isFoundationalCSharpCertV8': instance.isFoundationalCSharpCertV8, + 'joinDate': instance.joinDate.toIso8601String(), + 'points': instance.points, + 'calendar': instance.calendar, + 'completedChallenges': instance.completedChallenges, + 'completedDailyCodingChallenges': instance.completedDailyCodingChallenges, + 'savedChallenges': instance.savedChallenges, + 'portfolio': instance.portfolio, + 'yearsTopContributor': instance.yearsTopContributor, + }; diff --git a/mobile-app-new/lib/auth/repositories/session_repository.dart b/mobile-app-new/lib/auth/repositories/session_repository.dart new file mode 100644 index 000000000..93fdaf034 --- /dev/null +++ b/mobile-app-new/lib/auth/repositories/session_repository.dart @@ -0,0 +1,171 @@ +import 'dart:async'; + +import 'package:auth0_flutter/auth0_flutter.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:mobile_app_new/services/dio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'session_repository.g.dart'; + +typedef AuthTokens = ({String csrf, String csrfToken, String jwtAccessToken}); + +// NOTE: The API rejected the stored session (401), or there is none. +class SessionExpiredException implements Exception {} + +const _scopes = {'openid', 'profile', 'email'}; + +// v7's secure storage keys. +const _csrfKey = 'csrf'; +const _csrfTokenKey = 'csrf_token'; +const _jwtKey = 'jwt_access_token'; + +@Riverpod(keepAlive: true) +AuthSessionRepository authSessionRepository(Ref ref) { + return AuthSessionRepository(ref.watch(dioProvider)); +} + +class AuthSessionRepository { + AuthSessionRepository(this._dio); + + final Dio _dio; + final _store = const FlutterSecureStorage(); + + final _auth0 = Auth0( + dotenv.get('AUTH0_DOMAIN'), + dotenv.get('AUTH0_CLIENT_ID'), + ); + + AuthTokens? _tokens; + bool _tokensLoaded = false; + + final _sessionExpired = StreamController.broadcast(); + + Stream get sessionExpired => _sessionExpired.stream; + + String get _apiUrl => dotenv.getBool('DEVELOPMENT_MODE', fallback: false) + ? 'https://api.freecodecamp.dev' + : 'https://api.freecodecamp.org'; + + Future readTokens() async { + if (_tokensLoaded) return _tokens; + + final csrf = await _store.read(key: _csrfKey); + final csrfToken = await _store.read(key: _csrfTokenKey); + final jwt = await _store.read(key: _jwtKey); + _tokensLoaded = true; + + if (csrf == null || csrf.isEmpty) return null; + if (csrfToken == null || csrfToken.isEmpty) return null; + if (jwt == null || jwt.isEmpty) return null; + + _tokens = (csrf: csrf, csrfToken: csrfToken, jwtAccessToken: jwt); + + return _tokens; + } + + Future writeTokens(AuthTokens tokens) async { + await _store.write(key: _csrfKey, value: tokens.csrf); + await _store.write(key: _csrfTokenKey, value: tokens.csrfToken); + await _store.write(key: _jwtKey, value: tokens.jwtAccessToken); + _tokens = tokens; + _tokensLoaded = true; + } + + Future clearTokens() async { + await _store.delete(key: _csrfKey); + await _store.delete(key: _csrfTokenKey); + await _store.delete(key: _jwtKey); + _tokens = null; + _tokensLoaded = true; + } + + Future webAuthLogin(String connection) async { + // NOTE: The scheme is only needed for Android debug builds, whose callback URL differs. + final credentials = await _auth0 + .webAuthentication( + scheme: kReleaseMode ? null : 'org.freecodecamp', + useCredentialsManager: false, + ) + .login( + useHTTPS: true, + scopes: _scopes, + // NOTE: prompt=login ignores Auth0's remembered session, so logging out sticks. + parameters: {'connection': connection, 'prompt': 'login'}, + ); + + return credentials.accessToken; + } + + Future emailOtpStart(String email) { + return _auth0.api.startPasswordlessWithEmail( + email: email, + passwordlessType: PasswordlessType.code, + ); + } + + Future emailOtpExchange(String email, String otp) async { + final credentials = await _auth0.api.loginWithEmailCode( + email: email, + verificationCode: otp, + scopes: _scopes, + ); + + return credentials.accessToken; + } + + Future> mobileLogin(String accessToken) { + return _dio.get( + '$_apiUrl/mobile-login', + options: Options(headers: {'Authorization': 'Bearer $accessToken'}), + ); + } + + Future?> fetchSessionUser() async { + final res = await _authenticated( + (options) => _dio.get>( + '$_apiUrl/user/session-user', + options: options, + ), + ); + + final data = res.data; + final result = data?['result']; + if (result is! String || result.isEmpty) return null; + + return (data!['user'] as Map)[result] + as Map; + } + + Future deleteAccount() async { + await _authenticated( + (options) => _dio.post('$_apiUrl/account/delete', options: options), + ); + } + + // NOTE: Every call carrying the session goes through here, so any 401 signs the user out. + Future _authenticated(Future Function(Options) request) async { + final tokens = await readTokens(); + if (tokens == null) throw SessionExpiredException(); + + try { + return await request( + Options( + headers: { + 'CSRF-Token': tokens.csrfToken, + 'Cookie': + 'jwt_access_token=${tokens.jwtAccessToken}; _csrf=${tokens.csrf}', + }, + ), + ); + } on DioException catch (e) { + if (e.response?.statusCode != 401) rethrow; + + await clearTokens(); + _sessionExpired.add(null); + throw SessionExpiredException(); + } + } +} diff --git a/mobile-app-new/lib/auth/repositories/session_repository.g.dart b/mobile-app-new/lib/auth/repositories/session_repository.g.dart new file mode 100644 index 000000000..c5b1bc89a --- /dev/null +++ b/mobile-app-new/lib/auth/repositories/session_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'session_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(authSessionRepository) +final authSessionRepositoryProvider = AuthSessionRepositoryProvider._(); + +final class AuthSessionRepositoryProvider + extends + $FunctionalProvider< + AuthSessionRepository, + AuthSessionRepository, + AuthSessionRepository + > + with $Provider { + AuthSessionRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'authSessionRepositoryProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$authSessionRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + AuthSessionRepository create(Ref ref) { + return authSessionRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AuthSessionRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$authSessionRepositoryHash() => + r'018e6ced2ad1e3e4b86be9c24717a7f147975f4c'; diff --git a/mobile-app-new/lib/auth/services/session_service.dart b/mobile-app-new/lib/auth/services/session_service.dart new file mode 100644 index 000000000..3132c9431 --- /dev/null +++ b/mobile-app-new/lib/auth/services/session_service.dart @@ -0,0 +1,103 @@ +import 'dart:io'; + +import 'package:auth0_flutter/auth0_flutter.dart'; +import 'package:dio/dio.dart'; +import 'package:mobile_app_new/auth/models/user_model.dart'; +import 'package:mobile_app_new/auth/repositories/session_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'session_service.g.dart'; + +@Riverpod(keepAlive: true) +AuthSessionService authSessionService(Ref ref) { + return AuthSessionService(ref.watch(authSessionRepositoryProvider)); +} + +class InvalidOtpException implements Exception {} + +class AuthSessionService { + AuthSessionService(this._repo); + + final AuthSessionRepository _repo; + + Stream get sessionExpired => _repo.sessionExpired; + + // NOTE: Only a 401/403 clears the stored tokens; any other failure rethrows and keeps them. + Future restoreSession() async { + if (await _repo.readTokens() == null) return null; + + Map? json; + try { + json = await _repo.fetchSessionUser(); + } on SessionExpiredException { + return null; + } on DioException catch (e) { + if (e.response?.statusCode != 403) rethrow; + } + + if (json == null) { + await _repo.clearTokens(); + return null; + } + + return FccUserModel.fromJson(json); + } + + Future loginWithProvider(String connection) async { + return _completeLogin(await _repo.webAuthLogin(connection)); + } + + Future sendEmailOtp(String email) => _repo.emailOtpStart(email); + + Future loginWithEmailOtp(String email, String otp) async { + final String accessToken; + try { + accessToken = await _repo.emailOtpExchange(email, otp); + } on ApiException catch (e) { + final rejected = e.statusCode >= 400 && e.statusCode < 500; + if (rejected && !e.isNetworkError) throw InvalidOtpException(); + rethrow; + } + return _completeLogin(accessToken); + } + + Future logout() => _repo.clearTokens(); + + Future deleteAccount() async { + await _repo.deleteAccount(); + await _repo.clearTokens(); + } + + Future _completeLogin(String accessToken) async { + try { + final tokens = _extractTokens(await _repo.mobileLogin(accessToken)); + await _repo.writeTokens(tokens); + + final json = await _repo.fetchSessionUser(); + if (json == null) throw StateError('Session user missing after login'); + + return FccUserModel.fromJson(json); + } catch (_) { + await _repo.clearTokens(); + rethrow; + } + } + + AuthTokens _extractTokens(Response res) { + final headers = res.headers['set-cookie'] ?? const []; + final cookies = { + for (final cookie in headers.map(Cookie.fromSetCookieValue)) + cookie.name: cookie.value, + }; + + final csrf = cookies['_csrf']; + final csrfToken = cookies['csrf_token']; + final jwt = cookies['jwt_access_token']; + + if (csrf == null || csrfToken == null || jwt == null) { + throw StateError('mobile-login did not set the session cookies'); + } + + return (csrf: csrf, csrfToken: csrfToken, jwtAccessToken: jwt); + } +} diff --git a/mobile-app-new/lib/auth/services/session_service.g.dart b/mobile-app-new/lib/auth/services/session_service.g.dart new file mode 100644 index 000000000..cba39a552 --- /dev/null +++ b/mobile-app-new/lib/auth/services/session_service.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'session_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(authSessionService) +final authSessionServiceProvider = AuthSessionServiceProvider._(); + +final class AuthSessionServiceProvider + extends + $FunctionalProvider< + AuthSessionService, + AuthSessionService, + AuthSessionService + > + with $Provider { + AuthSessionServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'authSessionServiceProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$authSessionServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + AuthSessionService create(Ref ref) { + return authSessionService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AuthSessionService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$authSessionServiceHash() => + r'93398023ee6246232909b212abf32acc5cbf42ba'; diff --git a/mobile-app-new/lib/auth/ui/login/login_view.dart b/mobile-app-new/lib/auth/ui/login/login_view.dart new file mode 100644 index 000000000..a0ba9f2cc --- /dev/null +++ b/mobile-app-new/lib/auth/ui/login/login_view.dart @@ -0,0 +1,294 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/auth/ui/login/login_viewmodel.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/utils/custom_tab.dart'; +import 'package:mobile_app_new/widgets/section_divider.dart'; + +final _emailPattern = RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$', +); +final _otpPattern = RegExp(r'^[0-9]{6}$'); + +const _noticeStyle = TextStyle(color: Color(0xDEFFFFFF)); + +class LoginView extends ConsumerStatefulWidget { + const LoginView({super.key}); + + @override + ConsumerState createState() => _LoginViewState(); +} + +class _LoginViewState extends ConsumerState { + final _emailController = TextEditingController(); + final _otpController = TextEditingController(); + final _termsRecognizer = TapGestureRecognizer() + ..onTap = () => + openInCustomTab('https://www.freecodecamp.org/news/terms-of-service'); + final _privacyRecognizer = TapGestureRecognizer() + ..onTap = () => + openInCustomTab('https://www.freecodecamp.org/news/privacy-policy'); + + @override + void dispose() { + _emailController.dispose(); + _otpController.dispose(); + _termsRecognizer.dispose(); + _privacyRecognizer.dispose(); + super.dispose(); + } + + void _showError(String message) { + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); + } + + void _afterLogin(String? error) { + if (!mounted) return; + if (error != null) { + _showError(error); + } else if (ref.read(authProvider).isLoggedIn) { + context.pop(); + } + } + + Future _loginWithProvider(String connection) async { + _afterLogin( + await ref.read(loginProvider.notifier).loginWithProvider(connection), + ); + } + + Future _sendOtp() async { + final error = await ref + .read(loginProvider.notifier) + .sendOtp(_emailController.text); + if (mounted && error != null) _showError(error); + } + + Future _verifyOtp() async { + _afterLogin( + await ref + .read(loginProvider.notifier) + .verifyOtp(_emailController.text, _otpController.text), + ); + } + + @override + Widget build(BuildContext context) { + final state = ref.watch(loginProvider); + + return Scaffold( + appBar: AppBar(title: const Text('LOG IN')), + body: Stack( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.only(top: 16), + child: Column( + children: [ + _ProviderButton( + label: 'Continue with Google', + logo: 'assets/images/google-logo.png', + onPressed: () => _loginWithProvider('google-oauth2'), + ), + _ProviderButton( + label: 'Continue with GitHub', + logo: 'assets/images/github-logo.png', + onPressed: () => _loginWithProvider('github'), + ), + _ProviderButton( + label: 'Continue with Apple', + logo: 'assets/images/apple-logo.png', + onPressed: () => _loginWithProvider('apple'), + ), + sectionDivider, + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + enabled: !state.showOtpField, + controller: _emailController, + keyboardType: TextInputType.emailAddress, + decoration: const InputDecoration( + hintText: 'Email', + border: OutlineInputBorder( + borderSide: BorderSide(width: 2, color: Colors.white), + ), + ), + ), + ), + if (state.showOtpField) + Padding( + padding: const EdgeInsets.all(16), + child: TextFormField( + autofocus: true, + controller: _otpController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + hintText: 'Enter sign in code', + errorText: state.incorrectOtp + ? 'The code you entered is not valid. Please check the last OTP you received and try again.' + : null, + errorMaxLines: 5, + border: const OutlineInputBorder( + borderSide: BorderSide(width: 2, color: Colors.white), + ), + ), + ), + ), + ListenableBuilder( + listenable: Listenable.merge([ + _emailController, + _otpController, + ]), + builder: (context, _) { + final canSubmit = state.showOtpField + ? _otpPattern.hasMatch(_otpController.text) + : _emailPattern.hasMatch(_emailController.text); + + return _CtaButton( + onPressed: canSubmit + ? (state.showOtpField ? _verifyOtp : _sendOtp) + : null, + ); + }, + ), + sectionDivider, + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'freeCodeCamp is free and your account is private by default. We use your email address to connect you to your account.', + textAlign: TextAlign.center, + style: _noticeStyle, + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'You must be at least 13 years old to create an account on freeCodeCamp.', + textAlign: TextAlign.center, + style: _noticeStyle, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Text.rich( + textAlign: TextAlign.center, + style: _noticeStyle, + TextSpan( + children: [ + const TextSpan( + text: + "By continuing, you indicate that you have read and agree to freeCodeCamp.org's ", + ), + TextSpan( + text: 'Terms of Service', + style: const TextStyle( + decoration: TextDecoration.underline, + ), + recognizer: _termsRecognizer, + ), + const TextSpan(text: ' and '), + TextSpan( + text: 'Privacy Policy', + style: const TextStyle( + decoration: TextDecoration.underline, + ), + recognizer: _privacyRecognizer, + ), + const TextSpan(text: '.'), + ], + ), + ), + ), + ], + ), + ), + if (state.isLoading) + const ModalBarrier(dismissible: false, color: Colors.black54), + if (state.isLoading) const Center(child: CircularProgressIndicator()), + ], + ), + ); + } +} + +class _ProviderButton extends StatelessWidget { + const _ProviderButton({ + required this.label, + required this.logo, + required this.onPressed, + }); + + final String label; + final String logo; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + constraints: const BoxConstraints(minHeight: 50), + margin: const EdgeInsets.all(16), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + disabledBackgroundColor: Colors.grey, + shape: const RoundedRectangleBorder(), + side: const BorderSide(width: 3, color: Colors.black), + ), + onPressed: onPressed, + child: Row( + children: [ + Image.asset(logo, height: 25, width: 25), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + label, + style: const TextStyle(fontSize: 20, color: Colors.black), + ), + ), + ), + ], + ), + ), + ); + } +} + +class _CtaButton extends StatelessWidget { + const _CtaButton({required this.onPressed}); + + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + constraints: const BoxConstraints(minHeight: 50), + margin: const EdgeInsets.all(16), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: FccSemanticColors.backgroundCta, + foregroundColor: FccSemanticColors.foregroundCta, + // NOTE: backgroundCta at 80% opacity. + disabledBackgroundColor: const Color(0xCCFFC300), + disabledForegroundColor: FccSemanticColors.foregroundCta, + shape: const RoundedRectangleBorder(), + ), + onPressed: onPressed, + child: const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Submit and sign in to freeCodeCamp', + style: TextStyle(fontSize: 20), + textAlign: TextAlign.center, + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/auth/ui/login/login_viewmodel.dart b/mobile-app-new/lib/auth/ui/login/login_viewmodel.dart new file mode 100644 index 000000000..70f796eee --- /dev/null +++ b/mobile-app-new/lib/auth/ui/login/login_viewmodel.dart @@ -0,0 +1,77 @@ +import 'package:auth0_flutter/auth0_flutter.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/auth/services/session_service.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'login_viewmodel.freezed.dart'; +part 'login_viewmodel.g.dart'; + +@freezed +abstract class LoginState with _$LoginState { + const factory LoginState({ + @Default(false) bool showOtpField, + @Default(false) bool incorrectOtp, + @Default(false) bool isLoading, + }) = _LoginState; +} + +@riverpod +class LoginNotifier extends _$LoginNotifier { + @override + LoginState build() => const LoginState(); + + Future loginWithProvider(String connection) async { + try { + await _login( + ref.read(authProvider.notifier).loginWithProvider(connection), + ); + return null; + } catch (e) { + return e is WebAuthenticationException && e.isUserCancelledException + ? 'Login cancelled' + : 'Login failed. Please try again.'; + } + } + + Future sendOtp(String email) async { + state = state.copyWith(isLoading: true); + + try { + await ref.read(authSessionServiceProvider).sendEmailOtp(email); + if (ref.mounted) state = state.copyWith(showOtpField: true); + return null; + } catch (_) { + return 'Could not send the sign in code. Please try again.'; + } finally { + if (ref.mounted) state = state.copyWith(isLoading: false); + } + } + + // NOTE: A rejected code is shown on the field instead of returned. + Future verifyOtp(String email, String otp) async { + state = state.copyWith(incorrectOtp: false); + + try { + await _login( + ref.read(authProvider.notifier).loginWithEmailOtp(email, otp), + ); + return null; + } on InvalidOtpException { + if (ref.mounted) state = state.copyWith(incorrectOtp: true); + return null; + } catch (_) { + return 'Login failed. Please try again.'; + } + } + + Future _login(Future login) async { + state = state.copyWith(isLoading: true); + + try { + await login; + } finally { + if (ref.mounted) state = state.copyWith(isLoading: false); + } + } +} diff --git a/mobile-app-new/lib/auth/ui/login/login_viewmodel.freezed.dart b/mobile-app-new/lib/auth/ui/login/login_viewmodel.freezed.dart new file mode 100644 index 000000000..a8f0bac36 --- /dev/null +++ b/mobile-app-new/lib/auth/ui/login/login_viewmodel.freezed.dart @@ -0,0 +1,277 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'login_viewmodel.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$LoginState { + + bool get showOtpField; bool get incorrectOtp; bool get isLoading; +/// Create a copy of LoginState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$LoginStateCopyWith get copyWith => _$LoginStateCopyWithImpl(this as LoginState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is LoginState&&(identical(other.showOtpField, showOtpField) || other.showOtpField == showOtpField)&&(identical(other.incorrectOtp, incorrectOtp) || other.incorrectOtp == incorrectOtp)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,showOtpField,incorrectOtp,isLoading); + +@override +String toString() { + return 'LoginState(showOtpField: $showOtpField, incorrectOtp: $incorrectOtp, isLoading: $isLoading)'; +} + + +} + +/// @nodoc +abstract mixin class $LoginStateCopyWith<$Res> { + factory $LoginStateCopyWith(LoginState value, $Res Function(LoginState) _then) = _$LoginStateCopyWithImpl; +@useResult +$Res call({ + bool showOtpField, bool incorrectOtp, bool isLoading +}); + + + + +} +/// @nodoc +class _$LoginStateCopyWithImpl<$Res> + implements $LoginStateCopyWith<$Res> { + _$LoginStateCopyWithImpl(this._self, this._then); + + final LoginState _self; + final $Res Function(LoginState) _then; + +/// Create a copy of LoginState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? showOtpField = null,Object? incorrectOtp = null,Object? isLoading = null,}) { + return _then(_self.copyWith( +showOtpField: null == showOtpField ? _self.showOtpField : showOtpField // ignore: cast_nullable_to_non_nullable +as bool,incorrectOtp: null == incorrectOtp ? _self.incorrectOtp : incorrectOtp // ignore: cast_nullable_to_non_nullable +as bool,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [LoginState]. +extension LoginStatePatterns on LoginState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _LoginState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _LoginState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _LoginState value) $default,){ +final _that = this; +switch (_that) { +case _LoginState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _LoginState value)? $default,){ +final _that = this; +switch (_that) { +case _LoginState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( bool showOtpField, bool incorrectOtp, bool isLoading)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _LoginState() when $default != null: +return $default(_that.showOtpField,_that.incorrectOtp,_that.isLoading);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( bool showOtpField, bool incorrectOtp, bool isLoading) $default,) {final _that = this; +switch (_that) { +case _LoginState(): +return $default(_that.showOtpField,_that.incorrectOtp,_that.isLoading);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool showOtpField, bool incorrectOtp, bool isLoading)? $default,) {final _that = this; +switch (_that) { +case _LoginState() when $default != null: +return $default(_that.showOtpField,_that.incorrectOtp,_that.isLoading);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _LoginState implements LoginState { + const _LoginState({this.showOtpField = false, this.incorrectOtp = false, this.isLoading = false}); + + +@override@JsonKey() final bool showOtpField; +@override@JsonKey() final bool incorrectOtp; +@override@JsonKey() final bool isLoading; + +/// Create a copy of LoginState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$LoginStateCopyWith<_LoginState> get copyWith => __$LoginStateCopyWithImpl<_LoginState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LoginState&&(identical(other.showOtpField, showOtpField) || other.showOtpField == showOtpField)&&(identical(other.incorrectOtp, incorrectOtp) || other.incorrectOtp == incorrectOtp)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)); +} + + +@override +int get hashCode => Object.hash(runtimeType,showOtpField,incorrectOtp,isLoading); + +@override +String toString() { + return 'LoginState(showOtpField: $showOtpField, incorrectOtp: $incorrectOtp, isLoading: $isLoading)'; +} + + +} + +/// @nodoc +abstract mixin class _$LoginStateCopyWith<$Res> implements $LoginStateCopyWith<$Res> { + factory _$LoginStateCopyWith(_LoginState value, $Res Function(_LoginState) _then) = __$LoginStateCopyWithImpl; +@override @useResult +$Res call({ + bool showOtpField, bool incorrectOtp, bool isLoading +}); + + + + +} +/// @nodoc +class __$LoginStateCopyWithImpl<$Res> + implements _$LoginStateCopyWith<$Res> { + __$LoginStateCopyWithImpl(this._self, this._then); + + final _LoginState _self; + final $Res Function(_LoginState) _then; + +/// Create a copy of LoginState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? showOtpField = null,Object? incorrectOtp = null,Object? isLoading = null,}) { + return _then(_LoginState( +showOtpField: null == showOtpField ? _self.showOtpField : showOtpField // ignore: cast_nullable_to_non_nullable +as bool,incorrectOtp: null == incorrectOtp ? _self.incorrectOtp : incorrectOtp // ignore: cast_nullable_to_non_nullable +as bool,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/auth/ui/login/login_viewmodel.g.dart b/mobile-app-new/lib/auth/ui/login/login_viewmodel.g.dart new file mode 100644 index 000000000..22f349845 --- /dev/null +++ b/mobile-app-new/lib/auth/ui/login/login_viewmodel.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'login_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(LoginNotifier) +final loginProvider = LoginNotifierProvider._(); + +final class LoginNotifierProvider + extends $NotifierProvider { + LoginNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'loginProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$loginNotifierHash(); + + @$internal + @override + LoginNotifier create() => LoginNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(LoginState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$loginNotifierHash() => r'b2cb6d14360c6e6a4eeb05fde91b95ada20a0a19'; + +abstract class _$LoginNotifier extends $Notifier { + LoginState build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + LoginState, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/auth/ui/profile/profile_view.dart b/mobile-app-new/lib/auth/ui/profile/profile_view.dart new file mode 100644 index 000000000..c79ff3dda --- /dev/null +++ b/mobile-app-new/lib/auth/ui/profile/profile_view.dart @@ -0,0 +1,606 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_heatmap_calendar/flutter_heatmap_calendar.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:mobile_app_new/auth/models/user_model.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/auth/ui/profile/profile_viewmodel.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; +import 'package:url_launcher/url_launcher.dart'; + +({int longest, int current}) _calculateStreak(Map days) { + final sorted = days.keys.toList()..sort(); + var longest = 0; + var current = 0; + + for (final day in sorted) { + final previous = DateTime(day.year, day.month, day.day - 1); + current = days.containsKey(previous) ? current + 1 : 1; + if (current > longest) longest = current; + } + + // A streak whose last day is before yesterday has ended. + final now = DateTime.now(); + final yesterday = DateTime(now.year, now.month, now.day - 1); + if (sorted.isEmpty || sorted.last.isBefore(yesterday)) current = 0; + + return (longest: longest, current: current); +} + +class ProfileView extends ConsumerWidget { + const ProfileView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final refresh = ref.watch(profileRefreshProvider); + final user = ref.watch(authProvider.select((s) => s.value)); + + return Scaffold( + backgroundColor: FccSemanticColors.backgroundPrimary, + appBar: AppBar( + title: const Text('PROFILE'), + backgroundColor: FccSemanticColors.backgroundSecondary, + ), + drawer: const DrawerWidget(), + body: switch (refresh) { + AsyncData() when user != null => _ProfileBody(user: user), + AsyncData() => const Center( + child: Text( + 'No user data found', + style: TextStyle(color: FccSemanticColors.foregroundDanger), + ), + ), + AsyncError() => ErrorRetry( + message: 'Unable to load your profile.', + onRetry: () => ref.invalidate(profileRefreshProvider), + ), + _ => const Center( + child: CircularProgressIndicator(color: FccColors.yellow40), + ), + }, + ); + } +} + +class _ProfileBody extends StatelessWidget { + const _ProfileBody({required this.user}); + + final FccUserModel user; + + @override + Widget build(BuildContext context) { + final heatMapCal = user.heatMapCal; + final streak = _calculateStreak(heatMapCal); + + return SingleChildScrollView( + child: Column( + children: [ + _Header(user: user), + if (user.about?.trim().isNotEmpty ?? false) + _Section( + padding: const EdgeInsets.all(16), + child: _About(about: user.about!), + ), + _Section( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + child: _Info(user: user, streak: streak), + ), + _Section( + padding: const EdgeInsets.all(12), + child: _Heatmap(days: heatMapCal), + ), + if (user.portfolio.isNotEmpty) + _Section( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: Column( + children: [ + for (final item in user.portfolio) + _PortfolioCard(portfolio: item), + ], + ), + ), + _Section( + margin: const EdgeInsets.fromLTRB(8, 6, 8, 56), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: _Certifications(user: user), + ), + ], + ), + ); + } +} + +class _Section extends StatelessWidget { + const _Section({ + required this.padding, + required this.child, + this.margin = const EdgeInsets.symmetric(horizontal: 8, vertical: 6), + }); + + final EdgeInsets padding; + final EdgeInsets margin; + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + margin: margin, + child: Card( + color: FccColors.gray85, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Padding(padding: padding, child: child), + ), + ); + } +} + +class _Header extends StatelessWidget { + const _Header({required this.user}); + + final FccUserModel user; + + @override + Widget build(BuildContext context) { + final hasName = user.name.trim().isNotEmpty; + final nameColor = user.isDonating ? FccColors.yellow40 : Colors.white; + + return Stack( + children: [ + Container( + width: double.infinity, + margin: const EdgeInsets.fromLTRB(8, 89, 8, 6), + child: Card( + color: FccColors.gray85, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 92, 16, 24), + child: Column( + children: [ + Text( + hasName ? user.name : '@${user.username}', + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: nameColor, + ), + ), + if (hasName) ...[ + const SizedBox(height: 4), + Text( + '@${user.username}', + style: TextStyle( + fontSize: 16, + color: nameColor, + fontWeight: FontWeight.w600, + ), + ), + ], + ], + ), + ), + ), + ), + Center( + child: Stack( + children: [ + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: FccColors.blue50, width: 3), + ), + child: CircleAvatar( + radius: 54, + backgroundColor: FccColors.gray85, + backgroundImage: user.picture.isEmpty + ? const AssetImage( + 'assets/images/placeholder-profile-img.png', + ) + : CachedNetworkImageProvider(user.picture), + ), + ), + if (user.isDonating || user.yearsTopContributor.isNotEmpty) + Positioned( + bottom: 4, + right: 4, + child: Container( + decoration: BoxDecoration( + color: FccColors.yellow45, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2), + ), + padding: const EdgeInsets.all(4), + child: Icon( + user.isDonating ? Icons.favorite : Icons.emoji_events, + color: FccColors.gray85, + size: 18, + ), + ), + ), + ], + ), + ), + ], + ); + } +} + +class _About extends StatelessWidget { + const _About({required this.about}); + + final String about; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Icon(Icons.chat_bubble, color: FccColors.gray00, size: 22), + SizedBox(width: 10), + Text( + 'About', + style: TextStyle( + color: FccColors.gray00, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + about, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 15, + ), + ), + ], + ); + } +} + +class _Info extends StatelessWidget { + const _Info({required this.user, required this.streak}); + + final FccUserModel user; + final ({int longest, int current}) streak; + + @override + Widget build(BuildContext context) { + bool present(String? value) => value != null && value.trim().isNotEmpty; + + final rows = [ + if (present(user.location)) + _InfoRow( + Icons.location_on, + 'Location', + user.location!, + FccColors.blue50, + ), + if (user.isDonating) + const _InfoRow( + Icons.favorite, + 'Supporter', + 'Supporter', + FccColors.yellow45, + ), + if (present(user.twitter)) + _InfoRow(Icons.alternate_email, 'X', user.twitter!, FccColors.blue50), + if (present(user.githubProfile)) + _InfoRow(Icons.code, 'GitHub', user.githubProfile!, FccColors.blue50), + if (present(user.linkedin)) + _InfoRow(Icons.business, 'LinkedIn', user.linkedin!, FccColors.blue50), + if (present(user.website)) + _InfoRow(Icons.language, 'Website', user.website!, FccColors.blue50), + _InfoRow( + Icons.calendar_month, + 'Joined', + Jiffy.parseFromDateTime(user.joinDate).yMMMM, + FccColors.gray15, + ), + _InfoRow( + Icons.local_fire_department_sharp, + 'Points', + '${user.points}', + FccColors.red30, + ), + _InfoRow( + Icons.emoji_events, + 'Longest Streak', + '${streak.longest}', + FccColors.orange30, + ), + _InfoRow( + Icons.bolt, + 'Current Streak', + '${streak.current}', + FccColors.orange30, + ), + ]; + + return Column( + children: [ + for (final (i, row) in rows.indexed) ...[ + if (i > 0) + const Divider(color: FccColors.gray75, height: 18, thickness: 1), + row, + ], + ], + ); + } +} + +class _InfoRow extends StatelessWidget { + const _InfoRow(this.icon, this.label, this.value, this.iconColor); + + final IconData icon; + final String label; + final String value; + final Color iconColor; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Icon(icon, color: iconColor, size: 22), + const SizedBox(width: 14), + Expanded( + child: Text( + label, + style: const TextStyle( + color: FccColors.gray15, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ), + Flexible( + child: Text( + value, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + ), + ), + ], + ), + ); + } +} + +class _Heatmap extends StatelessWidget { + const _Heatmap({required this.days}); + + final Map days; + + @override + Widget build(BuildContext context) { + return HeatMap( + startDate: Jiffy.now().subtract(months: 3).dateTime, + datasets: days, + colorsets: const { + 0: FccColors.gray80, + 1: FccColors.gray45, + 4: FccColors.gray15, + 8: FccColors.gray00, + }, + defaultColor: FccColors.gray80, + scrollable: true, + colorMode: ColorMode.color, + showColorTip: false, + onClick: (date) { + final points = days[date] ?? 0; + final day = Jiffy.parseFromDateTime(date).yMMMd; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: FccColors.gray75, + duration: const Duration(seconds: 2), + content: Text( + '$points ${points == 1 ? 'point' : 'points'} on $day', + style: const TextStyle( + color: FccSemanticColors.foregroundPrimary, + ), + ), + ), + ); + }, + ); + } +} + +class _PortfolioCard extends StatelessWidget { + const _PortfolioCard({required this.portfolio}); + + final Portfolio portfolio; + + @override + Widget build(BuildContext context) { + final url = portfolio.url?.trim() ?? ''; + final link = url.isEmpty ? null : Uri.tryParse(url); + final image = portfolio.image; + final description = portfolio.description; + return Card( + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + color: FccColors.gray85, + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: InkWell( + onTap: link == null ? null : () => launchUrl(link).ignore(), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (image != null && image.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(12), + child: CachedNetworkImage( + imageUrl: image, + height: 150, + width: double.infinity, + fit: BoxFit.cover, + errorWidget: (_, _, _) => const SizedBox.shrink(), + ), + ), + const SizedBox(height: 12), + Text( + portfolio.title ?? '', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + if (description != null && description.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text( + description, + style: const TextStyle( + fontSize: 14, + color: FccColors.gray15, + ), + ), + ), + if (link != null) ...[ + const SizedBox(height: 12), + Row( + children: [ + const Icon(Icons.link, color: FccColors.blue50, size: 20), + const SizedBox(width: 8), + Expanded( + child: Text( + url, + style: const TextStyle( + fontSize: 14, + color: FccColors.blue50, + decoration: TextDecoration.underline, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ], + ), + ), + ), + ); + } +} + +class _Certifications extends StatelessWidget { + const _Certifications({required this.user}); + + final FccUserModel user; + + @override + Widget build(BuildContext context) { + final current = [ + if (user.isRespWebDesignCert) 'Responsive Web Design Certification', + if (user.isJsAlgoDataStructCert) + 'JavaScript Algorithms and Data Structures Certification', + if (user.isFrontEndLibsCert) + 'Front End Development Libraries Certification', + if (user.is2018DataVisCert) 'Data Visualization Certification', + if (user.isApisMicroservicesCert) + 'Back End Development and APIs Certification', + if (user.isQaCertV7) 'Quality Assurance Certification', + if (user.isInfosecCertV7) 'Information Security Certification', + if (user.isSciCompPyCertV7) + 'Scientific Computing with Python Certification', + if (user.isDataAnalysisPyCertV7) + 'Data Analysis with Python Certification', + if (user.isMachineLearningPyCertV7) + 'Machine Learning with Python Certification', + if (user.isRelationalDatabaseCertV8) 'Relational Database Certification', + if (user.isCollegeAlgebraPyCertV8) 'College Algebra with Python', + if (user.isFoundationalCSharpCertV8) 'Foundational C# with Microsoft', + ]; + + final legacy = [ + if (user.isFrontEndCert) 'Front End Certification', + if (user.isBackEndCert) 'Back End Certification', + if (user.isDataVisCert) 'Data Visualization Certification', + if (user.isInfosecQaCert) + 'Information Security and Quality Assurance Certification', + if (user.isFullStackCert) 'Full Stack Certification', + ]; + + final certs = [...current, ...legacy]; + + return Column( + children: [ + const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + 'freeCodeCamp Certifications', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + height: 1.25, + fontWeight: FontWeight.w700, + ), + ), + ), + if (certs.isEmpty) + const Text( + 'You have not yet earned any certifications.', + style: TextStyle(fontSize: 16, color: Colors.white), + textAlign: TextAlign.center, + ), + for (final title in certs) _Trophy(title: title), + ], + ); + } +} + +class _Trophy extends StatelessWidget { + const _Trophy({required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 6), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + color: FccColors.gray85, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + const Icon(Icons.emoji_events, color: FccColors.yellow40, size: 26), + const SizedBox(width: 14), + Expanded( + child: Text( + title, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 15, + ), + ), + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/auth/ui/profile/profile_viewmodel.dart b/mobile-app-new/lib/auth/ui/profile/profile_viewmodel.dart new file mode 100644 index 000000000..b1836f7d1 --- /dev/null +++ b/mobile-app-new/lib/auth/ui/profile/profile_viewmodel.dart @@ -0,0 +1,9 @@ +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'profile_viewmodel.g.dart'; + +@riverpod +Future profileRefresh(Ref ref) { + return ref.read(authProvider.notifier).refresh(); +} diff --git a/mobile-app-new/lib/auth/ui/profile/profile_viewmodel.g.dart b/mobile-app-new/lib/auth/ui/profile/profile_viewmodel.g.dart new file mode 100644 index 000000000..ae84c775a --- /dev/null +++ b/mobile-app-new/lib/auth/ui/profile/profile_viewmodel.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'profile_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(profileRefresh) +final profileRefreshProvider = ProfileRefreshProvider._(); + +final class ProfileRefreshProvider + extends $FunctionalProvider, void, FutureOr> + with $FutureModifier, $FutureProvider { + ProfileRefreshProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'profileRefreshProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$profileRefreshHash(); + + @$internal + @override + $FutureProviderElement $createElement($ProviderPointer pointer) => + $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + return profileRefresh(ref); + } +} + +String _$profileRefreshHash() => r'7a23bf3bf8e7cc2011690cdf77401ce8ac0796f1'; diff --git a/mobile-app-new/lib/learn/models/challenge_file_model.dart b/mobile-app-new/lib/learn/models/challenge_file_model.dart new file mode 100644 index 000000000..58814a847 --- /dev/null +++ b/mobile-app-new/lib/learn/models/challenge_file_model.dart @@ -0,0 +1,26 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'challenge_file_model.freezed.dart'; +part 'challenge_file_model.g.dart'; + +enum Ext { js, html, css, jsx, py } + +Object? _readFileKey(Map json, String key) => + json[key] ?? '${json['name']}${json['ext']}'; + +@freezed +abstract class ChallengeFile with _$ChallengeFile { + const factory ChallengeFile({ + @JsonKey(unknownEnumValue: Ext.html) required Ext ext, + required String name, + String? head, + String? tail, + required String contents, + @Default([]) List history, + @Default([]) List editableRegionBoundaries, + @JsonKey(readValue: _readFileKey) required String fileKey, + }) = _ChallengeFile; + + factory ChallengeFile.fromJson(Map json) => + _$ChallengeFileFromJson(json); +} diff --git a/mobile-app-new/lib/learn/models/challenge_file_model.freezed.dart b/mobile-app-new/lib/learn/models/challenge_file_model.freezed.dart new file mode 100644 index 000000000..b40783341 --- /dev/null +++ b/mobile-app-new/lib/learn/models/challenge_file_model.freezed.dart @@ -0,0 +1,310 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'challenge_file_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$ChallengeFile { + +@JsonKey(unknownEnumValue: Ext.html) Ext get ext; String get name; String? get head; String? get tail; String get contents; List get history; List get editableRegionBoundaries;@JsonKey(readValue: _readFileKey) String get fileKey; +/// Create a copy of ChallengeFile +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ChallengeFileCopyWith get copyWith => _$ChallengeFileCopyWithImpl(this as ChallengeFile, _$identity); + + /// Serializes this ChallengeFile to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ChallengeFile&&(identical(other.ext, ext) || other.ext == ext)&&(identical(other.name, name) || other.name == name)&&(identical(other.head, head) || other.head == head)&&(identical(other.tail, tail) || other.tail == tail)&&(identical(other.contents, contents) || other.contents == contents)&&const DeepCollectionEquality().equals(other.history, history)&&const DeepCollectionEquality().equals(other.editableRegionBoundaries, editableRegionBoundaries)&&(identical(other.fileKey, fileKey) || other.fileKey == fileKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,ext,name,head,tail,contents,const DeepCollectionEquality().hash(history),const DeepCollectionEquality().hash(editableRegionBoundaries),fileKey); + +@override +String toString() { + return 'ChallengeFile(ext: $ext, name: $name, head: $head, tail: $tail, contents: $contents, history: $history, editableRegionBoundaries: $editableRegionBoundaries, fileKey: $fileKey)'; +} + + +} + +/// @nodoc +abstract mixin class $ChallengeFileCopyWith<$Res> { + factory $ChallengeFileCopyWith(ChallengeFile value, $Res Function(ChallengeFile) _then) = _$ChallengeFileCopyWithImpl; +@useResult +$Res call({ +@JsonKey(unknownEnumValue: Ext.html) Ext ext, String name, String? head, String? tail, String contents, List history, List editableRegionBoundaries,@JsonKey(readValue: _readFileKey) String fileKey +}); + + + + +} +/// @nodoc +class _$ChallengeFileCopyWithImpl<$Res> + implements $ChallengeFileCopyWith<$Res> { + _$ChallengeFileCopyWithImpl(this._self, this._then); + + final ChallengeFile _self; + final $Res Function(ChallengeFile) _then; + +/// Create a copy of ChallengeFile +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? ext = null,Object? name = null,Object? head = freezed,Object? tail = freezed,Object? contents = null,Object? history = null,Object? editableRegionBoundaries = null,Object? fileKey = null,}) { + return _then(_self.copyWith( +ext: null == ext ? _self.ext : ext // ignore: cast_nullable_to_non_nullable +as Ext,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,head: freezed == head ? _self.head : head // ignore: cast_nullable_to_non_nullable +as String?,tail: freezed == tail ? _self.tail : tail // ignore: cast_nullable_to_non_nullable +as String?,contents: null == contents ? _self.contents : contents // ignore: cast_nullable_to_non_nullable +as String,history: null == history ? _self.history : history // ignore: cast_nullable_to_non_nullable +as List,editableRegionBoundaries: null == editableRegionBoundaries ? _self.editableRegionBoundaries : editableRegionBoundaries // ignore: cast_nullable_to_non_nullable +as List,fileKey: null == fileKey ? _self.fileKey : fileKey // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ChallengeFile]. +extension ChallengeFilePatterns on ChallengeFile { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ChallengeFile value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ChallengeFile() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ChallengeFile value) $default,){ +final _that = this; +switch (_that) { +case _ChallengeFile(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ChallengeFile value)? $default,){ +final _that = this; +switch (_that) { +case _ChallengeFile() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(unknownEnumValue: Ext.html) Ext ext, String name, String? head, String? tail, String contents, List history, List editableRegionBoundaries, @JsonKey(readValue: _readFileKey) String fileKey)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ChallengeFile() when $default != null: +return $default(_that.ext,_that.name,_that.head,_that.tail,_that.contents,_that.history,_that.editableRegionBoundaries,_that.fileKey);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function(@JsonKey(unknownEnumValue: Ext.html) Ext ext, String name, String? head, String? tail, String contents, List history, List editableRegionBoundaries, @JsonKey(readValue: _readFileKey) String fileKey) $default,) {final _that = this; +switch (_that) { +case _ChallengeFile(): +return $default(_that.ext,_that.name,_that.head,_that.tail,_that.contents,_that.history,_that.editableRegionBoundaries,_that.fileKey);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(unknownEnumValue: Ext.html) Ext ext, String name, String? head, String? tail, String contents, List history, List editableRegionBoundaries, @JsonKey(readValue: _readFileKey) String fileKey)? $default,) {final _that = this; +switch (_that) { +case _ChallengeFile() when $default != null: +return $default(_that.ext,_that.name,_that.head,_that.tail,_that.contents,_that.history,_that.editableRegionBoundaries,_that.fileKey);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ChallengeFile implements ChallengeFile { + const _ChallengeFile({@JsonKey(unknownEnumValue: Ext.html) required this.ext, required this.name, this.head, this.tail, required this.contents, final List history = const [], final List editableRegionBoundaries = const [], @JsonKey(readValue: _readFileKey) required this.fileKey}): _history = history,_editableRegionBoundaries = editableRegionBoundaries; + factory _ChallengeFile.fromJson(Map json) => _$ChallengeFileFromJson(json); + +@override@JsonKey(unknownEnumValue: Ext.html) final Ext ext; +@override final String name; +@override final String? head; +@override final String? tail; +@override final String contents; + final List _history; +@override@JsonKey() List get history { + if (_history is EqualUnmodifiableListView) return _history; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_history); +} + + final List _editableRegionBoundaries; +@override@JsonKey() List get editableRegionBoundaries { + if (_editableRegionBoundaries is EqualUnmodifiableListView) return _editableRegionBoundaries; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_editableRegionBoundaries); +} + +@override@JsonKey(readValue: _readFileKey) final String fileKey; + +/// Create a copy of ChallengeFile +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ChallengeFileCopyWith<_ChallengeFile> get copyWith => __$ChallengeFileCopyWithImpl<_ChallengeFile>(this, _$identity); + +@override +Map toJson() { + return _$ChallengeFileToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChallengeFile&&(identical(other.ext, ext) || other.ext == ext)&&(identical(other.name, name) || other.name == name)&&(identical(other.head, head) || other.head == head)&&(identical(other.tail, tail) || other.tail == tail)&&(identical(other.contents, contents) || other.contents == contents)&&const DeepCollectionEquality().equals(other._history, _history)&&const DeepCollectionEquality().equals(other._editableRegionBoundaries, _editableRegionBoundaries)&&(identical(other.fileKey, fileKey) || other.fileKey == fileKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,ext,name,head,tail,contents,const DeepCollectionEquality().hash(_history),const DeepCollectionEquality().hash(_editableRegionBoundaries),fileKey); + +@override +String toString() { + return 'ChallengeFile(ext: $ext, name: $name, head: $head, tail: $tail, contents: $contents, history: $history, editableRegionBoundaries: $editableRegionBoundaries, fileKey: $fileKey)'; +} + + +} + +/// @nodoc +abstract mixin class _$ChallengeFileCopyWith<$Res> implements $ChallengeFileCopyWith<$Res> { + factory _$ChallengeFileCopyWith(_ChallengeFile value, $Res Function(_ChallengeFile) _then) = __$ChallengeFileCopyWithImpl; +@override @useResult +$Res call({ +@JsonKey(unknownEnumValue: Ext.html) Ext ext, String name, String? head, String? tail, String contents, List history, List editableRegionBoundaries,@JsonKey(readValue: _readFileKey) String fileKey +}); + + + + +} +/// @nodoc +class __$ChallengeFileCopyWithImpl<$Res> + implements _$ChallengeFileCopyWith<$Res> { + __$ChallengeFileCopyWithImpl(this._self, this._then); + + final _ChallengeFile _self; + final $Res Function(_ChallengeFile) _then; + +/// Create a copy of ChallengeFile +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? ext = null,Object? name = null,Object? head = freezed,Object? tail = freezed,Object? contents = null,Object? history = null,Object? editableRegionBoundaries = null,Object? fileKey = null,}) { + return _then(_ChallengeFile( +ext: null == ext ? _self.ext : ext // ignore: cast_nullable_to_non_nullable +as Ext,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,head: freezed == head ? _self.head : head // ignore: cast_nullable_to_non_nullable +as String?,tail: freezed == tail ? _self.tail : tail // ignore: cast_nullable_to_non_nullable +as String?,contents: null == contents ? _self.contents : contents // ignore: cast_nullable_to_non_nullable +as String,history: null == history ? _self._history : history // ignore: cast_nullable_to_non_nullable +as List,editableRegionBoundaries: null == editableRegionBoundaries ? _self._editableRegionBoundaries : editableRegionBoundaries // ignore: cast_nullable_to_non_nullable +as List,fileKey: null == fileKey ? _self.fileKey : fileKey // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/learn/models/challenge_file_model.g.dart b/mobile-app-new/lib/learn/models/challenge_file_model.g.dart new file mode 100644 index 000000000..01cc5032b --- /dev/null +++ b/mobile-app-new/lib/learn/models/challenge_file_model.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'challenge_file_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_ChallengeFile _$ChallengeFileFromJson(Map json) => + _ChallengeFile( + ext: $enumDecode(_$ExtEnumMap, json['ext'], unknownValue: Ext.html), + name: json['name'] as String, + head: json['head'] as String?, + tail: json['tail'] as String?, + contents: json['contents'] as String, + history: + (json['history'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + editableRegionBoundaries: + (json['editableRegionBoundaries'] as List?) + ?.map((e) => (e as num).toInt()) + .toList() ?? + const [], + fileKey: _readFileKey(json, 'fileKey') as String, + ); + +Map _$ChallengeFileToJson(_ChallengeFile instance) => + { + 'ext': _$ExtEnumMap[instance.ext]!, + 'name': instance.name, + 'head': instance.head, + 'tail': instance.tail, + 'contents': instance.contents, + 'history': instance.history, + 'editableRegionBoundaries': instance.editableRegionBoundaries, + 'fileKey': instance.fileKey, + }; + +const _$ExtEnumMap = { + Ext.js: 'js', + Ext.html: 'html', + Ext.css: 'css', + Ext.jsx: 'jsx', + Ext.py: 'py', +}; diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index 028e2dd87..f0435e235 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -7,6 +7,7 @@ import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/podcasts/services/library_service.dart'; import 'package:mobile_app_new/routing/router.dart'; import 'package:mobile_app_new/services/audio_service.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; import 'package:mobile_app_new/services/notification_service.dart'; Future main() async { @@ -36,6 +37,8 @@ class _MyAppState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) { unawaited(ref.read(notificationServiceProvider).requestPermission()); + // NOTE: Starts restoring the stored session so the drawer rarely sees it loading. + ref.read(authProvider); // NOTE: Deletes audio and artwork that no store row points at unawaited(ref.read(podcastLibraryServiceProvider).sweepOrphanedFiles()); }); diff --git a/mobile-app-new/lib/routing/auth.dart b/mobile-app-new/lib/routing/auth.dart new file mode 100644 index 000000000..39a4aea92 --- /dev/null +++ b/mobile-app-new/lib/routing/auth.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/auth/ui/login/login_view.dart'; +import 'package:mobile_app_new/auth/ui/profile/profile_view.dart'; + +part 'auth.g.dart'; + +const loginPath = '/login'; +const profilePath = '/profile'; + +@TypedGoRoute(path: loginPath) +class LoginRoute extends GoRouteData with $LoginRoute { + const LoginRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => const LoginView(); +} + +@TypedGoRoute(path: profilePath) +class ProfileRoute extends GoRouteData with $ProfileRoute { + const ProfileRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const ProfileView(); +} diff --git a/mobile-app-new/lib/routing/auth.g.dart b/mobile-app-new/lib/routing/auth.g.dart new file mode 100644 index 000000000..fa9f86519 --- /dev/null +++ b/mobile-app-new/lib/routing/auth.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [$loginRoute, $profileRoute]; + +RouteBase get $loginRoute => GoRouteData.$route( + path: '/login', + hasOverriddenOnExit: false, + factory: $LoginRoute._fromState, +); + +mixin $LoginRoute on GoRouteData { + static LoginRoute _fromState(GoRouterState state) => const LoginRoute(); + + @override + String get location => GoRouteData.$location('/login'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $profileRoute => GoRouteData.$route( + path: '/profile', + hasOverriddenOnExit: false, + factory: $ProfileRoute._fromState, +); + +mixin $ProfileRoute on GoRouteData { + static ProfileRoute _fromState(GoRouterState state) => const ProfileRoute(); + + @override + String get location => GoRouteData.$location('/profile'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/mobile-app-new/lib/routing/router.dart b/mobile-app-new/lib/routing/router.dart index 7a716288e..3870aa4b7 100644 --- a/mobile-app-new/lib/routing/router.dart +++ b/mobile-app-new/lib/routing/router.dart @@ -1,10 +1,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/routing/auth.dart' as auth_routes; import 'package:mobile_app_new/routing/code_radio.dart' as code_radio_routes; import 'package:mobile_app_new/routing/learn.dart' as learn_routes; import 'package:mobile_app_new/routing/news.dart' as news_routes; import 'package:mobile_app_new/routing/podcasts.dart' as podcasts_routes; +import 'package:mobile_app_new/routing/settings.dart' as settings_routes; final _initialLocation = const learn_routes.LearnLandingRoute().location; @@ -16,6 +18,8 @@ final router = GoRouter( ...news_routes.$appRoutes, ...code_radio_routes.$appRoutes, ...podcasts_routes.$appRoutes, + ...auth_routes.$appRoutes, + ...settings_routes.$appRoutes, ], errorBuilder: (context, state) => Scaffold( appBar: AppBar(title: const Text('Page Not Found')), diff --git a/mobile-app-new/lib/routing/settings.dart b/mobile-app-new/lib/routing/settings.dart new file mode 100644 index 000000000..93c52f4a9 --- /dev/null +++ b/mobile-app-new/lib/routing/settings.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/settings/ui/settings/delete_account/delete_account_view.dart'; +import 'package:mobile_app_new/settings/ui/settings/settings_view.dart'; + +part 'settings.g.dart'; + +const settingsPath = '/settings'; +const settingsDeleteAccountPath = '/settings/delete-account'; + +@TypedGoRoute(path: settingsPath) +class SettingsRoute extends GoRouteData with $SettingsRoute { + const SettingsRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const SettingsView(); +} + +@TypedGoRoute(path: settingsDeleteAccountPath) +class SettingsDeleteAccountRoute extends GoRouteData + with $SettingsDeleteAccountRoute { + const SettingsDeleteAccountRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const DeleteAccountView(); +} diff --git a/mobile-app-new/lib/routing/settings.g.dart b/mobile-app-new/lib/routing/settings.g.dart new file mode 100644 index 000000000..ab17d8be1 --- /dev/null +++ b/mobile-app-new/lib/routing/settings.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'settings.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [$settingsRoute, $settingsDeleteAccountRoute]; + +RouteBase get $settingsRoute => GoRouteData.$route( + path: '/settings', + hasOverriddenOnExit: false, + factory: $SettingsRoute._fromState, +); + +mixin $SettingsRoute on GoRouteData { + static SettingsRoute _fromState(GoRouterState state) => const SettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $settingsDeleteAccountRoute => GoRouteData.$route( + path: '/settings/delete-account', + hasOverriddenOnExit: false, + factory: $SettingsDeleteAccountRoute._fromState, +); + +mixin $SettingsDeleteAccountRoute on GoRouteData { + static SettingsDeleteAccountRoute _fromState(GoRouterState state) => + const SettingsDeleteAccountRoute(); + + @override + String get location => GoRouteData.$location('/settings/delete-account'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} diff --git a/mobile-app-new/lib/services/auth_service.dart b/mobile-app-new/lib/services/auth_service.dart new file mode 100644 index 000000000..0890dd306 --- /dev/null +++ b/mobile-app-new/lib/services/auth_service.dart @@ -0,0 +1,59 @@ +import 'package:mobile_app_new/auth/models/user_model.dart'; +import 'package:mobile_app_new/auth/services/session_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'auth_service.g.dart'; + +extension AuthStatus on AsyncValue { + bool get isLoggedIn => value != null; +} + +Duration? _noRetry(int retryCount, Object error) => null; + +// NOTE: The signed-in user, or null when signed out. The only place auth state changes. +@Riverpod(keepAlive: true, retry: _noRetry) +class AuthNotifier extends _$AuthNotifier { + @override + Future build() { + final session = ref.watch(authSessionServiceProvider); + + // NOTE: Any authenticated call rejected with a 401 signs the user out everywhere. + final sub = session.sessionExpired.listen((_) { + state = const AsyncData(null); + }); + ref.onDispose(sub.cancel); + + return session.restoreSession(); + } + + AuthSessionService get _session => ref.read(authSessionServiceProvider); + + Future loginWithProvider(String connection) async { + state = AsyncData(await _session.loginWithProvider(connection)); + } + + Future loginWithEmailOtp(String email, String otp) async { + state = AsyncData(await _session.loginWithEmailOtp(email, otp)); + } + + Future refresh() async { + if (state.hasError) { + ref.invalidateSelf(); + return; + } + if (!state.isLoggedIn) return; + + final user = await _session.restoreSession(); + if (ref.mounted && state.isLoggedIn) state = AsyncData(user); + } + + Future signOut() async { + await _session.logout(); + state = const AsyncData(null); + } + + Future deleteAccount() async { + await _session.deleteAccount(); + state = const AsyncData(null); + } +} diff --git a/mobile-app-new/lib/services/auth_service.g.dart b/mobile-app-new/lib/services/auth_service.g.dart new file mode 100644 index 000000000..2271ce721 --- /dev/null +++ b/mobile-app-new/lib/services/auth_service.g.dart @@ -0,0 +1,54 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(AuthNotifier) +final authProvider = AuthNotifierProvider._(); + +final class AuthNotifierProvider + extends $AsyncNotifierProvider { + AuthNotifierProvider._() + : super( + from: null, + argument: null, + retry: _noRetry, + name: r'authProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$authNotifierHash(); + + @$internal + @override + AuthNotifier create() => AuthNotifier(); +} + +String _$authNotifierHash() => r'8a83aa80bb58addb8d06a66c4d893a193e3734a3'; + +abstract class _$AuthNotifier extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref, FccUserModel?>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, FccUserModel?>, + AsyncValue, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_view.dart b/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_view.dart new file mode 100644 index 000000000..5fb73ccf7 --- /dev/null +++ b/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_view.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/routing/learn.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/settings/ui/settings/delete_account/delete_account_viewmodel.dart'; +import 'package:mobile_app_new/widgets/dialog/confirm_dialog.dart'; +import 'package:mobile_app_new/widgets/section_divider.dart'; +import 'package:url_launcher/url_launcher.dart'; + +const _paragraphStyle = TextStyle(fontSize: 18, height: 1.2); + +class DeleteAccountView extends ConsumerWidget { + const DeleteAccountView({super.key}); + + Future _delete(BuildContext context, WidgetRef ref) async { + final confirmed = await showConfirmDialog( + context, + title: 'Delete Account', + description: + 'Are you sure you want to delete your account? - this deletes everything related to your account', + confirmLabel: 'Delete Account', + destructive: true, + ); + + if (!confirmed || !context.mounted) return; + + final messenger = ScaffoldMessenger.of(context); + final message = await ref + .read(deleteAccountProvider.notifier) + .deleteAccount(); + + messenger.showSnackBar(SnackBar(content: Text(message))); + + // NOTE: Signed out means the account was deleted or the session expired. + if (context.mounted && !ref.read(authProvider).isLoggedIn) { + const LearnLandingRoute().go(context); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isDeleting = ref.watch(deleteAccountProvider); + + return PopScope( + canPop: !isDeleting, + child: Scaffold( + appBar: AppBar(title: const Text('Delete Account')), + body: Stack( + children: [ + SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const Text( + 'This will really delete all your data, including all your progress and account information.', + style: _paragraphStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + const Text( + "We won't be able to recover any of it for you later, even if you change your mind.", + style: _paragraphStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + const Text( + "If there's something we could do better, send us an email instead and we'll do our best:", + style: _paragraphStyle, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + InkWell( + onTap: () => + launchUrl(Uri.parse('mailto:support@freecodecamp.org')), + child: Text( + 'support@freecodecamp.org', + style: _paragraphStyle.copyWith( + decoration: TextDecoration.underline, + ), + ), + ), + const SizedBox(height: 12), + sectionDivider, + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: TextButton( + onPressed: isDeleting + ? null + : () => _delete(context, ref), + style: TextButton.styleFrom( + backgroundColor: Colors.red.shade100, + foregroundColor: Colors.red.shade900, + side: BorderSide(width: 2, color: Colors.red.shade900), + disabledBackgroundColor: Colors.red.shade50, + disabledForegroundColor: Colors.red.shade700, + shape: const RoundedRectangleBorder(), + ), + child: const Text( + 'I am 100% certain. Delete everything related to this account', + textAlign: TextAlign.center, + style: _paragraphStyle, + ), + ), + ), + ], + ), + ), + if (isDeleting) + const ModalBarrier(dismissible: false, color: Colors.black54), + if (isDeleting) const Center(child: CircularProgressIndicator()), + ], + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.dart b/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.dart new file mode 100644 index 000000000..a71d8c988 --- /dev/null +++ b/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.dart @@ -0,0 +1,27 @@ +import 'package:mobile_app_new/auth/repositories/session_repository.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'delete_account_viewmodel.g.dart'; + +@riverpod +class DeleteAccountNotifier extends _$DeleteAccountNotifier { + @override + bool build() => false; + + Future deleteAccount() async { + final auth = ref.read(authProvider.notifier); + state = true; + + try { + await auth.deleteAccount(); + return 'Account deleted successfully'; + } on SessionExpiredException { + return 'Your session has expired. Please log in again.'; + } catch (_) { + return 'Account deletion failed. Please try again later.'; + } finally { + if (ref.mounted) state = false; + } + } +} diff --git a/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.g.dart b/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.g.dart new file mode 100644 index 000000000..000d74ba2 --- /dev/null +++ b/mobile-app-new/lib/settings/ui/settings/delete_account/delete_account_viewmodel.g.dart @@ -0,0 +1,63 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'delete_account_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(DeleteAccountNotifier) +final deleteAccountProvider = DeleteAccountNotifierProvider._(); + +final class DeleteAccountNotifierProvider + extends $NotifierProvider { + DeleteAccountNotifierProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'deleteAccountProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$deleteAccountNotifierHash(); + + @$internal + @override + DeleteAccountNotifier create() => DeleteAccountNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(bool value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$deleteAccountNotifierHash() => + r'2a476c024e549495fc997667db5a4b27b9d06461'; + +abstract class _$DeleteAccountNotifier extends $Notifier { + bool build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + bool, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/settings/ui/settings/settings_view.dart b/mobile-app-new/lib/settings/ui/settings/settings_view.dart new file mode 100644 index 000000000..85e01f8cf --- /dev/null +++ b/mobile-app-new/lib/settings/ui/settings/settings_view.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/routing/settings.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/utils/custom_tab.dart'; +import 'package:mobile_app_new/widgets/dialog/confirm_dialog.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; +import 'package:mobile_app_new/widgets/section_divider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SettingsView extends ConsumerWidget { + const SettingsView({super.key}); + + Future _confirmResetCache(BuildContext context) async { + final confirmed = await showConfirmDialog( + context, + title: 'Reset Cache', + description: 'Clears all local data and progress', + confirmLabel: 'Clear', + ); + + if (!confirmed) return; + + final prefs = await SharedPreferences.getInstance(); + await prefs.clear(); + + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Cache cleared'))); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isLoggedIn = ref.watch(authProvider.select((s) => s.isLoggedIn)); + + return Scaffold( + appBar: AppBar(title: const Text('SETTINGS')), + drawer: const DrawerWidget(), + body: Column( + children: [ + if (isLoggedIn) ...[ + ListTile( + leading: const Icon(Icons.delete_forever), + title: const Text('Delete Account'), + subtitle: const Text('Delete your freeCodeCamp account'), + onTap: () => + const SettingsDeleteAccountRoute().push(context), + ), + sectionDivider, + ], + ListTile( + leading: const Icon(Icons.dataset_linked), + title: const Text('Reset Cache'), + subtitle: const Text('Clears all local data and progress'), + onTap: () => _confirmResetCache(context), + ), + sectionDivider, + ListTile( + leading: const Icon(Icons.privacy_tip), + title: const Text('Privacy Policy'), + subtitle: const Text('Read our privacy policy'), + onTap: () => openInCustomTab( + 'https://www.freecodecamp.org/news/privacy-policy/', + ), + ), + sectionDivider, + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/utils/custom_tab.dart b/mobile-app-new/lib/utils/custom_tab.dart new file mode 100644 index 000000000..681a92b94 --- /dev/null +++ b/mobile-app-new/lib/utils/custom_tab.dart @@ -0,0 +1,18 @@ +import 'package:flutter_custom_tabs/flutter_custom_tabs.dart'; + +Future openInCustomTab(String url) { + return launchUrl( + Uri.parse(url), + customTabsOptions: const CustomTabsOptions( + shareState: CustomTabsShareState.on, + urlBarHidingEnabled: true, + showTitle: true, + browser: CustomTabsBrowserConfiguration( + fallbackCustomTabs: ['org.mozilla.firefox', 'com.microsoft.emmx'], + ), + ), + safariVCOptions: const SafariViewControllerOptions( + barCollapsingEnabled: true, + ), + ); +} diff --git a/mobile-app-new/lib/widgets/dialog/confirm_dialog.dart b/mobile-app-new/lib/widgets/dialog/confirm_dialog.dart new file mode 100644 index 000000000..ad981c364 --- /dev/null +++ b/mobile-app-new/lib/widgets/dialog/confirm_dialog.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; + +final _buttonStyle = ElevatedButton.styleFrom( + backgroundColor: FccColors.gray75, + foregroundColor: FccColors.gray00, + side: const BorderSide(width: 2, color: FccColors.gray00), + shape: const RoundedRectangleBorder(), + minimumSize: const Size.fromHeight(50), +); + +final _destructiveStyle = ElevatedButton.styleFrom( + backgroundColor: Colors.red.shade100, + foregroundColor: Colors.red.shade900, + side: BorderSide(width: 2, color: Colors.red.shade900), + shape: const RoundedRectangleBorder(), + minimumSize: const Size.fromHeight(50), +); + +// NOTE: Resolves to true only when the confirm button is tapped. +Future showConfirmDialog( + BuildContext context, { + required String title, + required String description, + required String confirmLabel, + bool destructive = false, +}) async { + final confirmed = await showDialog( + context: context, + builder: (context) => Dialog( + backgroundColor: FccColors.gray90, + shape: const RoundedRectangleBorder(), + child: Padding( + padding: const EdgeInsets.all(16), + child: Wrap( + runSpacing: 20, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 24, + height: 1.5, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + Text( + description, + style: const TextStyle( + fontSize: 16, + height: 1.5, + color: Colors.white, + ), + ), + ElevatedButton( + style: destructive ? _destructiveStyle : _buttonStyle, + onPressed: () => Navigator.pop(context, true), + child: Text( + destructive ? confirmLabel.toUpperCase() : confirmLabel, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + fontWeight: destructive ? FontWeight.bold : null, + ), + ), + ), + ElevatedButton( + style: _buttonStyle, + onPressed: () => Navigator.pop(context, false), + child: const Text( + 'Cancel', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ), + ], + ), + ), + ), + ); + return confirmed ?? false; +} diff --git a/mobile-app-new/lib/widgets/drawer/drawer.dart b/mobile-app-new/lib/widgets/drawer/drawer.dart index d3b681cdf..16f91e528 100644 --- a/mobile-app-new/lib/widgets/drawer/drawer.dart +++ b/mobile-app-new/lib/widgets/drawer/drawer.dart @@ -1,43 +1,150 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/routing/auth.dart'; import 'package:mobile_app_new/routing/code_radio.dart'; import 'package:mobile_app_new/routing/learn.dart'; import 'package:mobile_app_new/routing/news.dart'; import 'package:mobile_app_new/routing/podcasts.dart'; +import 'package:mobile_app_new/routing/settings.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/utils/custom_tab.dart'; import 'package:mobile_app_new/widgets/drawer/drawer_tile.dart'; +import 'package:mobile_app_new/widgets/drawer/scroll_shadow.dart'; +import 'package:mobile_app_new/widgets/section_divider.dart'; -class DrawerWidget extends StatelessWidget { +class DrawerWidget extends ConsumerWidget { const DrawerWidget({super.key}); + void _login(BuildContext context) { + Scaffold.of(context).closeDrawer(); + const LoginRoute().push(context); + } + + // NOTE: Only Profile needs a signed-in user, so only it is left behind. + void _signOut(BuildContext context, WidgetRef ref) { + if (GoRouterState.of(context).uri.path == profilePath) { + const LearnLandingRoute().go(context); + } + unawaited(ref.read(authProvider.notifier).signOut()); + } + @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final auth = ref.watch(authProvider); + return Drawer( backgroundColor: FccColors.gray80, - child: ListView( + child: Column( children: [ - DrawerTile( - component: 'Home', - icon: Icons.home, - route: () => LearnLandingRoute().go(context), - ), - DrawerTile( - component: 'Tutorials', - icon: Icons.forum_outlined, - route: () => const NewsFeedRoute().go(context), - ), - DrawerTile( - component: 'Podcast', - icon: Icons.podcasts, - route: () => const PodcastListRoute().go(context), + Expanded( + child: SafeArea( + bottom: false, + child: ScrollShadow( + child: ListView( + children: [ + ListTile( + contentPadding: const EdgeInsets.all(16), + leading: Image.asset( + 'assets/images/placeholder-profile-img.png', + width: 75, + height: 75, + ), + title: Text(switch (auth.value) { + _ when auth.isLoading => 'Loading your account…', + _ when auth.hasError => "Couldn't load your account", + null => 'Anonymous User', + final user when user.name.isEmpty => user.username, + final user => user.name, + }), + subtitle: auth.isLoading + ? null + : Text(switch (auth) { + _ when auth.hasError => 'Tap to retry', + _ when auth.isLoggedIn => 'Our coolest Camper', + _ => 'Sign in to save your progress', + }), + onTap: switch (auth) { + _ when auth.isLoading => null, + _ when auth.hasError => () => unawaited( + ref.read(authProvider.notifier).refresh(), + ), + _ when auth.isLoggedIn => () => const ProfileRoute().go( + context, + ), + _ => () => _login(context), + }, + ), + sectionDivider, + DrawerTile( + component: 'Learn', + route: () => const LearnLandingRoute().go(context), + ), + DrawerTile( + component: 'Tutorials', + icon: Icons.forum_outlined, + route: () => const NewsFeedRoute().go(context), + ), + DrawerTile( + component: 'Podcast', + icon: Icons.podcasts, + route: () => const PodcastListRoute().go(context), + ), + if (!Platform.isIOS) + DrawerTile( + component: 'Code Radio', + icon: Icons.radio, + route: () => const CodeRadioRoute().go(context), + ), + sectionDivider, + DrawerTile( + component: 'Donate', + icon: Icons.favorite, + route: () => openInCustomTab( + 'https://www.freecodecamp.org/donate/', + ), + ), + DrawerTile( + component: 'Settings', + icon: Icons.settings, + route: () => const SettingsRoute().go(context), + ), + sectionDivider, + DrawerTile( + component: 'Report an issue', + icon: Icons.bug_report, + route: () => openInCustomTab( + 'https://github.com/freeCodeCamp/mobile', + ), + ), + if (!auth.isLoading && !auth.hasError) + DrawerTile( + component: auth.isLoggedIn ? 'Log Out' : 'Log In', + icon: auth.isLoggedIn ? Icons.logout : Icons.login, + textColor: auth.isLoggedIn + ? FccColors.red80 + : Colors.white, + route: () => auth.isLoggedIn + ? _signOut(context, ref) + : _login(context), + ), + ], + ), + ), + ), ), - if (!Platform.isIOS) - DrawerTile( - component: 'Code Radio', - icon: Icons.radio, - route: () => const CodeRadioRoute().go(context), + const SafeArea( + minimum: EdgeInsets.only(bottom: 16), + child: Text( + 'freeCodeCamp is a donor-supported tax-exempt 501(c)(3) nonprofit organization', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 10, color: Colors.white70), ), + ), ], ), ); diff --git a/mobile-app-new/lib/widgets/drawer/scroll_shadow.dart b/mobile-app-new/lib/widgets/drawer/scroll_shadow.dart new file mode 100644 index 000000000..180d2ff06 --- /dev/null +++ b/mobile-app-new/lib/widgets/drawer/scroll_shadow.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; + +const _color = Colors.black; +const _size = 6.0; + +// NOTE: Fades a shadow in at the top/bottom edge while there is more to scroll that way. +class ScrollShadow extends StatefulWidget { + const ScrollShadow({super.key, required this.child}); + + final Widget child; + + @override + State createState() => _ScrollShadowState(); +} + +class _ScrollShadowState extends State { + bool _showTop = false; + bool _showBottom = false; + + bool _onMetrics(ScrollMetrics metrics) { + final showTop = metrics.pixels > metrics.minScrollExtent; + final showBottom = metrics.pixels < metrics.maxScrollExtent; + + if (showTop != _showTop || showBottom != _showBottom) { + setState(() { + _showTop = showTop; + _showBottom = showBottom; + }); + } + return false; + } + + Widget _shadow({required bool visible, required bool top}) { + return Positioned( + top: top ? 0 : null, + bottom: top ? null : 0, + left: 0, + right: 0, + child: IgnorePointer( + child: AnimatedOpacity( + opacity: visible ? 1 : 0, + duration: const Duration(milliseconds: 250), + curve: visible ? Curves.easeIn : Curves.easeOut, + child: Container( + height: _size, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: top ? Alignment.topCenter : Alignment.bottomCenter, + end: top ? Alignment.bottomCenter : Alignment.topCenter, + colors: [_color, _color.withAlpha(0)], + ), + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + NotificationListener( + onNotification: (notification) => _onMetrics(notification.metrics), + child: NotificationListener( + onNotification: (notification) => _onMetrics(notification.metrics), + child: widget.child, + ), + ), + _shadow(visible: _showTop, top: true), + _shadow(visible: _showBottom, top: false), + ], + ); + } +} diff --git a/mobile-app-new/lib/widgets/section_divider.dart b/mobile-app-new/lib/widgets/section_divider.dart new file mode 100644 index 000000000..d46e25a89 --- /dev/null +++ b/mobile-app-new/lib/widgets/section_divider.dart @@ -0,0 +1,8 @@ +import 'package:flutter/material.dart'; + +const sectionDivider = Divider( + color: Color(0x1FFFFFFF), + indent: 16, + endIndent: 16, + thickness: 1, +); diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index affc3cdf7..85dcf69f8 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -145,6 +145,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.4" + auth0_flutter: + dependency: "direct main" + description: + name: auth0_flutter + sha256: "3f0cc91376005557afdaef7c1fb2b62a4faaa6c96e525224439480b76394ec4d" + url: "https://pub.dev" + source: hosted + version: "2.6.2" + auth0_flutter_platform_interface: + dependency: transitive + description: + name: auth0_flutter_platform_interface + sha256: "4bcac858ec60603c5e97b48d118f85051951a9bc0d72efc71d1190b267687427" + url: "https://pub.dev" + source: hosted + version: "2.6.2" boolean_selector: dependency: transitive description: @@ -414,6 +430,46 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.2" + flutter_custom_tabs: + dependency: "direct main" + description: + name: flutter_custom_tabs + sha256: abd00c87e26bc0415a61fa3985653444e9cc16b6a90584aad0d252abb3802532 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + flutter_custom_tabs_android: + dependency: transitive + description: + name: flutter_custom_tabs_android + sha256: "01557f40deeade9bf2969e093c55b827558ab28cfd71c58b73358bc0ab02b3da" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + flutter_custom_tabs_ios: + dependency: transitive + description: + name: flutter_custom_tabs_ios + sha256: "70e69a00dd03666a073b1e0b5bc11c3401ed12e557e048498abf65f1f7c2ee38" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + flutter_custom_tabs_platform_interface: + dependency: transitive + description: + name: flutter_custom_tabs_platform_interface + sha256: ef8fac8160d81f562d749a921e2dfeea4ccaad18df329b1797803c1874abcba1 + url: "https://pub.dev" + source: hosted + version: "2.5.0" + flutter_custom_tabs_web: + dependency: transitive + description: + name: flutter_custom_tabs_web + sha256: "7b1f2cfc024f355cbb42cd704e56669b23996c79de3a7cc518af8a3c8e805b46" + url: "https://pub.dev" + source: hosted + version: "2.5.0" flutter_dotenv: dependency: "direct main" description: @@ -427,6 +483,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_heatmap_calendar: + dependency: "direct main" + description: + name: flutter_heatmap_calendar + sha256: "0933071844cd604b938e38358984244292ba1ba8f4b4b6f0f091f5927a23e958" + url: "https://pub.dev" + source: hosted + version: "1.0.5" flutter_highlight: dependency: transitive description: @@ -515,6 +579,54 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.2" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: d4e1fb6b2cb524868929e78dc0282fa000554b22060fb53789dc481c9fc95bb8 + url: "https://pub.dev" + source: hosted + version: "11.2.0" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: a031ceac9b070e62ef183cd7f7d1a8798ebde402d5049f0fe6eb3a51bd797a21 + url: "https://pub.dev" + source: hosted + version: "0.4.3" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: caa75bd78f017422912e3a904933113b2428eb79f981ac37acd2dd2a700458ea + url: "https://pub.dev" + source: hosted + version: "3.0.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "64951127f001f546891c86f414972bf49ef2c4c8a4c92c65f5790cc0c9100045" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "073a62b3aeb866ab4ce795f960413948e51e5a42a9b0c8333b6daf5bb3208a1c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "471951813a97006d899db4948acc654a4f28c440083ea08178935ce20b173ec1" + url: "https://pub.dev" + source: hosted + version: "4.2.2" flutter_test: dependency: "direct dev" description: flutter diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index d84684b27..6a8e0b453 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -62,6 +62,10 @@ dependencies: web_socket_channel: 3.0.3 shared_preferences: 2.5.5 flutter_local_notifications: 22.3.0 + auth0_flutter: 2.6.2 + flutter_secure_storage: 11.2.0 + flutter_custom_tabs: 2.6.0 + flutter_heatmap_calendar: 1.0.5 dev_dependencies: flutter_driver: From d018a7385b4a3cfce43556c2a4d1c88b0f5abbce Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 11 Oct 2026 20:04:14 +0530 Subject: [PATCH 37/52] Learn 2/4 --- mobile-app-new/analysis_options.yaml | 9 + mobile-app-new/assets/learn/a1-chinese.svg | 8 + mobile-app-new/assets/learn/a1-spanish.svg | 8 + mobile-app-new/assets/learn/a2-english.svg | 8 + mobile-app-new/assets/learn/algorithm.svg | 3 + mobile-app-new/assets/learn/analytics.svg | 3 + mobile-app-new/assets/learn/api.svg | 3 + mobile-app-new/assets/learn/b1-english.svg | 8 + mobile-app-new/assets/learn/c-sharp.svg | 3 + mobile-app-new/assets/learn/clipboard.svg | 3 + mobile-app-new/assets/learn/code.svg | 3 + .../assets/learn/college-algebra.svg | 3 + mobile-app-new/assets/learn/d3.svg | 3 + mobile-app-new/assets/learn/database.svg | 3 + mobile-app-new/assets/learn/graduation.svg | 3 + mobile-app-new/assets/learn/javascript.svg | 3 + .../assets/learn/motivational-quotes.json | 716 +++++++ mobile-app-new/assets/learn/python.svg | 3 + mobile-app-new/assets/learn/react.svg | 3 + .../assets/learn/responsive-design.svg | 3 + mobile-app-new/assets/learn/rosetta-code.svg | 3 + mobile-app-new/assets/learn/shield.svg | 3 + mobile-app-new/assets/learn/tensorflow.svg | 3 + mobile-app-new/assets/learn/viking-helmet.svg | 3 + .../xcshareddata/swiftpm/Package.resolved | 117 ++ .../xcshareddata/swiftpm/Package.resolved | 117 ++ mobile-app-new/lib/firebase_options.dart | 69 + .../lib/learn/constants/curriculum.dart | 62 + .../controllers/curriculum_controller.dart | 22 + .../controllers/curriculum_controller.g.dart | 125 ++ .../lib/learn/models/curriculum_model.dart | 121 ++ .../models/curriculum_model.freezed.dart | 1687 +++++++++++++++++ .../lib/learn/models/curriculum_model.g.dart | 13 + .../learn/models/daily_challenge_model.dart | 22 + .../models/daily_challenge_model.freezed.dart | 286 +++ .../learn/models/daily_challenge_model.g.dart | 25 + .../repositories/curriculum_repository.dart | 51 + .../repositories/curriculum_repository.g.dart | 58 + .../daily_challenge_repository.dart | 36 + .../daily_challenge_repository.g.dart | 61 + .../learn/repositories/quote_repository.dart | 24 + .../repositories/quote_repository.g.dart | 58 + .../learn/services/curriculum_service.dart | 166 ++ .../learn/services/curriculum_service.g.dart | 58 + .../services/daily_challenge_service.dart | 47 + .../services/daily_challenge_service.g.dart | 59 + .../lib/learn/services/quote_service.dart | 25 + .../lib/learn/services/quote_service.g.dart | 57 + .../challenge/challenge_placeholder_view.dart | 20 + .../daily_challenge_archive_view.dart | 242 +++ .../daily_challenge_archive_viewmodel.dart | 8 + .../daily_challenge_archive_viewmodel.g.dart | 52 + mobile-app-new/lib/learn/ui/home_page.dart | 89 - .../ui/landing/daily_challenge_card.dart | 218 +++ .../lib/learn/ui/landing/landing_view.dart | 192 ++ .../learn/ui/landing/landing_viewmodel.dart | 14 + .../learn/ui/landing/landing_viewmodel.g.dart | 93 + .../learn/ui/landing/superblock_button.dart | 70 + .../lib/learn/ui/module/module_view.dart | 49 + .../learn/ui/superblock/superblock_view.dart | 303 +++ .../lib/learn/ui/widgets/block_card.dart | 504 +++++ mobile-app-new/lib/main.dart | 5 + .../lib/news/ui/widgets/post_feed_list.dart | 3 +- .../ui/episode_list/episode_list_view.dart | 3 +- .../ui/podcast_list/podcast_list_view.dart | 4 +- mobile-app-new/lib/routing/learn.dart | 85 +- mobile-app-new/lib/routing/learn.g.dart | 169 +- .../lib/services/remote_config_service.dart | 72 + .../remote_config_service.freezed.dart | 277 +++ .../lib/services/remote_config_service.g.dart | 55 + mobile-app-new/lib/widgets/drawer/drawer.dart | 6 + mobile-app-new/pubspec.lock | 98 +- mobile-app-new/pubspec.yaml | 5 + 73 files changed, 6708 insertions(+), 105 deletions(-) create mode 100644 mobile-app-new/assets/learn/a1-chinese.svg create mode 100644 mobile-app-new/assets/learn/a1-spanish.svg create mode 100644 mobile-app-new/assets/learn/a2-english.svg create mode 100644 mobile-app-new/assets/learn/algorithm.svg create mode 100644 mobile-app-new/assets/learn/analytics.svg create mode 100644 mobile-app-new/assets/learn/api.svg create mode 100644 mobile-app-new/assets/learn/b1-english.svg create mode 100644 mobile-app-new/assets/learn/c-sharp.svg create mode 100644 mobile-app-new/assets/learn/clipboard.svg create mode 100644 mobile-app-new/assets/learn/code.svg create mode 100644 mobile-app-new/assets/learn/college-algebra.svg create mode 100644 mobile-app-new/assets/learn/d3.svg create mode 100644 mobile-app-new/assets/learn/database.svg create mode 100644 mobile-app-new/assets/learn/graduation.svg create mode 100644 mobile-app-new/assets/learn/javascript.svg create mode 100644 mobile-app-new/assets/learn/motivational-quotes.json create mode 100644 mobile-app-new/assets/learn/python.svg create mode 100644 mobile-app-new/assets/learn/react.svg create mode 100644 mobile-app-new/assets/learn/responsive-design.svg create mode 100644 mobile-app-new/assets/learn/rosetta-code.svg create mode 100644 mobile-app-new/assets/learn/shield.svg create mode 100644 mobile-app-new/assets/learn/tensorflow.svg create mode 100644 mobile-app-new/assets/learn/viking-helmet.svg create mode 100644 mobile-app-new/lib/firebase_options.dart create mode 100644 mobile-app-new/lib/learn/constants/curriculum.dart create mode 100644 mobile-app-new/lib/learn/controllers/curriculum_controller.dart create mode 100644 mobile-app-new/lib/learn/controllers/curriculum_controller.g.dart create mode 100644 mobile-app-new/lib/learn/models/curriculum_model.dart create mode 100644 mobile-app-new/lib/learn/models/curriculum_model.freezed.dart create mode 100644 mobile-app-new/lib/learn/models/curriculum_model.g.dart create mode 100644 mobile-app-new/lib/learn/models/daily_challenge_model.dart create mode 100644 mobile-app-new/lib/learn/models/daily_challenge_model.freezed.dart create mode 100644 mobile-app-new/lib/learn/models/daily_challenge_model.g.dart create mode 100644 mobile-app-new/lib/learn/repositories/curriculum_repository.dart create mode 100644 mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart create mode 100644 mobile-app-new/lib/learn/repositories/daily_challenge_repository.dart create mode 100644 mobile-app-new/lib/learn/repositories/daily_challenge_repository.g.dart create mode 100644 mobile-app-new/lib/learn/repositories/quote_repository.dart create mode 100644 mobile-app-new/lib/learn/repositories/quote_repository.g.dart create mode 100644 mobile-app-new/lib/learn/services/curriculum_service.dart create mode 100644 mobile-app-new/lib/learn/services/curriculum_service.g.dart create mode 100644 mobile-app-new/lib/learn/services/daily_challenge_service.dart create mode 100644 mobile-app-new/lib/learn/services/daily_challenge_service.g.dart create mode 100644 mobile-app-new/lib/learn/services/quote_service.dart create mode 100644 mobile-app-new/lib/learn/services/quote_service.g.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/challenge_placeholder_view.dart create mode 100644 mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_view.dart create mode 100644 mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.dart create mode 100644 mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.g.dart delete mode 100644 mobile-app-new/lib/learn/ui/home_page.dart create mode 100644 mobile-app-new/lib/learn/ui/landing/daily_challenge_card.dart create mode 100644 mobile-app-new/lib/learn/ui/landing/landing_view.dart create mode 100644 mobile-app-new/lib/learn/ui/landing/landing_viewmodel.dart create mode 100644 mobile-app-new/lib/learn/ui/landing/landing_viewmodel.g.dart create mode 100644 mobile-app-new/lib/learn/ui/landing/superblock_button.dart create mode 100644 mobile-app-new/lib/learn/ui/module/module_view.dart create mode 100644 mobile-app-new/lib/learn/ui/superblock/superblock_view.dart create mode 100644 mobile-app-new/lib/learn/ui/widgets/block_card.dart create mode 100644 mobile-app-new/lib/services/remote_config_service.dart create mode 100644 mobile-app-new/lib/services/remote_config_service.freezed.dart create mode 100644 mobile-app-new/lib/services/remote_config_service.g.dart diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml index ade444ad8..f9a460a10 100644 --- a/mobile-app-new/analysis_options.yaml +++ b/mobile-app-new/analysis_options.yaml @@ -28,6 +28,15 @@ linter: # https://dart.dev/guides/language/analysis-options analyzer: + exclude: + # Flutter generated files + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** language: strict-raw-types: true strict-casts: true diff --git a/mobile-app-new/assets/learn/a1-chinese.svg b/mobile-app-new/assets/learn/a1-chinese.svg new file mode 100644 index 000000000..f0b3fe49f --- /dev/null +++ b/mobile-app-new/assets/learn/a1-chinese.svg @@ -0,0 +1,8 @@ + diff --git a/mobile-app-new/assets/learn/a1-spanish.svg b/mobile-app-new/assets/learn/a1-spanish.svg new file mode 100644 index 000000000..f08bcdbf9 --- /dev/null +++ b/mobile-app-new/assets/learn/a1-spanish.svg @@ -0,0 +1,8 @@ + diff --git a/mobile-app-new/assets/learn/a2-english.svg b/mobile-app-new/assets/learn/a2-english.svg new file mode 100644 index 000000000..3c78ec8b3 --- /dev/null +++ b/mobile-app-new/assets/learn/a2-english.svg @@ -0,0 +1,8 @@ + diff --git a/mobile-app-new/assets/learn/algorithm.svg b/mobile-app-new/assets/learn/algorithm.svg new file mode 100644 index 000000000..3ae51bfae --- /dev/null +++ b/mobile-app-new/assets/learn/algorithm.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/analytics.svg b/mobile-app-new/assets/learn/analytics.svg new file mode 100644 index 000000000..76c3f372f --- /dev/null +++ b/mobile-app-new/assets/learn/analytics.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/api.svg b/mobile-app-new/assets/learn/api.svg new file mode 100644 index 000000000..14bdd5b8a --- /dev/null +++ b/mobile-app-new/assets/learn/api.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/b1-english.svg b/mobile-app-new/assets/learn/b1-english.svg new file mode 100644 index 000000000..257c04a20 --- /dev/null +++ b/mobile-app-new/assets/learn/b1-english.svg @@ -0,0 +1,8 @@ + diff --git a/mobile-app-new/assets/learn/c-sharp.svg b/mobile-app-new/assets/learn/c-sharp.svg new file mode 100644 index 000000000..c5d705776 --- /dev/null +++ b/mobile-app-new/assets/learn/c-sharp.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/clipboard.svg b/mobile-app-new/assets/learn/clipboard.svg new file mode 100644 index 000000000..3654a91d4 --- /dev/null +++ b/mobile-app-new/assets/learn/clipboard.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/code.svg b/mobile-app-new/assets/learn/code.svg new file mode 100644 index 000000000..04c4d4f4a --- /dev/null +++ b/mobile-app-new/assets/learn/code.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/mobile-app-new/assets/learn/college-algebra.svg b/mobile-app-new/assets/learn/college-algebra.svg new file mode 100644 index 000000000..f29626a52 --- /dev/null +++ b/mobile-app-new/assets/learn/college-algebra.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/d3.svg b/mobile-app-new/assets/learn/d3.svg new file mode 100644 index 000000000..d07179107 --- /dev/null +++ b/mobile-app-new/assets/learn/d3.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/database.svg b/mobile-app-new/assets/learn/database.svg new file mode 100644 index 000000000..91875ce9e --- /dev/null +++ b/mobile-app-new/assets/learn/database.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/graduation.svg b/mobile-app-new/assets/learn/graduation.svg new file mode 100644 index 000000000..910de1e17 --- /dev/null +++ b/mobile-app-new/assets/learn/graduation.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/javascript.svg b/mobile-app-new/assets/learn/javascript.svg new file mode 100644 index 000000000..3d68eb024 --- /dev/null +++ b/mobile-app-new/assets/learn/javascript.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/motivational-quotes.json b/mobile-app-new/assets/learn/motivational-quotes.json new file mode 100644 index 000000000..300c29f21 --- /dev/null +++ b/mobile-app-new/assets/learn/motivational-quotes.json @@ -0,0 +1,716 @@ +{ + "motivationalQuotes": [ + { + "quote": "Whatever you are, be a good one.", + "author": "Abraham Lincoln" + }, + { + "quote": "A change in perspective is worth 80 IQ points.", + "author": "Alan Kay" + }, + { + "quote": "The best way to predict the future is to invent it.", + "author": "Alan Kay" + }, + { + "quote": "The future is not laid out on a track. It is something that we can decide, and to the extent that we do not violate any known laws of the universe, we can probably make it work the way that we want to.", + "author": "Alan Kay" + }, + { + "quote": "We can only see a short distance ahead, but we can see plenty there that needs to be done.", + "author": "Alan Turing" + }, + { + "quote": "In the depth of winter, I finally learned that within me there lay an invincible summer.", + "author": "Albert Camus" + }, + { + "quote": "A person who never made a mistake never tried anything new.", + "author": "Albert Einstein" + }, + { + "quote": "Creativity is intelligence having fun.", + "author": "Albert Einstein" + }, + { + "quote": "I have no special talents. I am only passionately curious.", + "author": "Albert Einstein" + }, + { + "quote": "Life is like riding a bicycle. To keep your balance, you must keep moving.", + "author": "Albert Einstein" + }, + { + "quote": "Make everything as simple as possible, but not simpler.", + "author": "Albert Einstein" + }, + { + "quote": "Never memorize something that you can look up.", + "author": "Albert Einstein" + }, + { + "quote": "Once we accept our limits, we go beyond them.", + "author": "Albert Einstein" + }, + { + "quote": "Play is the highest form of research.", + "author": "Albert Einstein" + }, + { + "quote": "We cannot solve our problems with the same thinking we used when we created them.", + "author": "Albert Einstein" + }, + { + "quote": "Wisdom is not a product of schooling but of the lifelong attempt to acquire it.", + "author": "Albert Einstein" + }, + { + "quote": "Your imagination is your preview of life's coming attractions.", + "author": "Albert Einstein" + }, + { + "quote": "There is only one corner of the universe you can be certain of improving, and that's your own self.", + "author": "Aldous Huxley" + }, + { + "quote": "The most common way people give up their power is by thinking they don't have any.", + "author": "Alice Walker" + }, + { + "quote": "Follow your inner moonlight. Don't hide the madness.", + "author": "Allen Ginsberg" + }, + { + "quote": "The most difficult thing is the decision to act. The rest is merely tenacity.", + "author": "Amelia Earhart" + }, + { + "quote": "Life shrinks or expands in proportion with one's courage.", + "author": "Anaïs Nin" + }, + { + "quote": "Weeks of programming can save you hours of planning.", + "author": "Unknown" + }, + { + "quote": "Quality is not an act, it is a habit.", + "author": "Aristotle" + }, + { + "quote": "Start where you are. Use what you have. Do what you can.", + "author": "Arthur Ashe" + }, + { + "quote": "Nothing is impossible, the word itself says \"I'm possible\"!", + "author": "Audrey Hepburn" + }, + { + "quote": "Every strike brings me closer to the next home run.", + "author": "Babe Ruth" + }, + { + "quote": "By failing to prepare, you are preparing to fail.", + "author": "Benjamin Franklin" + }, + { + "quote": "Tell me and I forget. Teach me and I remember. Involve me and I learn.", + "author": "Benjamin Franklin" + }, + { + "quote": "Well done is better than well said.", + "author": "Benjamin Franklin" + }, + { + "quote": "There are no short cuts to any place worth going.", + "author": "Beverly Sills" + }, + { + "quote": "Controlling complexity is the essence of computer programming.", + "author": "Brian Kernighan" + }, + { + "quote": "I fear not the man who has practiced 10,000 kicks once, but I fear the man who has practiced one kick 10,000 times.", + "author": "Bruce Lee" + }, + { + "quote": "There are far, far better things ahead than any we leave behind.", + "author": "C.S. Lewis" + }, + { + "quote": "We are what we believe we are.", + "author": "C.S. Lewis" + }, + { + "quote": "With the possible exception of the equator, everything begins somewhere.", + "author": "C.S. Lewis" + }, + { + "quote": "You are never too old to set another goal, or to dream a new dream.", + "author": "C.S. Lewis" + }, + { + "quote": "Somewhere, something incredible is waiting to be known.", + "author": "Carl Sagan" + }, + { + "quote": "If you're not making mistakes, then you're not making decisions.", + "author": "Catherine Cook" + }, + { + "quote": "Find what you love and let it kill you.", + "author": "Charles Bukowski" + }, + { + "quote": "What matters most is how well you walk through the fire.", + "author": "Charles Bukowski" + }, + { + "quote": "It is not the strongest of the species that survive, nor the most intelligent, but the one most responsive to change.", + "author": "Charles Darwin" + }, + { + "quote": "Life is 10% what happens to you and 90% how you react to it.", + "author": "Charles R. Swindoll" + }, + { + "quote": "You will do foolish things, but do them with enthusiasm.", + "author": "Colette" + }, + { + "quote": "It does not matter how slowly you go as long as you do not stop.", + "author": "Confucius" + }, + { + "quote": "Real knowledge is to know the extent of one's ignorance.", + "author": "Confucius" + }, + { + "quote": "The past cannot be changed. The future is yet in your power.", + "author": "Confucius" + }, + { + "quote": "Looking at code you wrote more than two weeks ago is like looking at code you are seeing for the first time.", + "author": "Dan Hurvitz" + }, + { + "quote": "Someday is not a day of the week.", + "author": "Denise Brennan-Nelson" + }, + { + "quote": "UNIX is simple. It just takes a genius to understand its simplicity.", + "author": "Dennis Ritchie" + }, + { + "quote": "Computers are good at following instructions, but not at reading your mind.", + "author": "Donald Knuth" + }, + { + "quote": "A good programmer is someone who always looks both ways before crossing a one-way street.", + "author": "Doug Linder" + }, + { + "quote": "Tough times never last, but tough people do.", + "author": "Dr. Robert Schuller" + }, + { + "quote": "If things start happening, don't worry, don't stew, just go right along and you'll start happening too.", + "author": "Dr. Seuss" + }, + { + "quote": "Do not go gentle into that good night. Rage, rage against the dying of the light.", + "author": "Dylan Thomas" + }, + { + "quote": "The question of whether computers can think is like the question of whether submarines can swim.", + "author": "E.W. Dijkstra" + }, + { + "quote": "Any code of your own that you haven't looked at for six or more months might as well have been written by someone else.", + "author": "Eagleson's Law" + }, + { + "quote": "Do one thing every day that scares you.", + "author": "Eleanor Roosevelt" + }, + { + "quote": "With the new day comes new strength and new thoughts.", + "author": "Eleanor Roosevelt" + }, + { + "quote": "You must do the things you think you cannot do.", + "author": "Eleanor Roosevelt" + }, + { + "quote": "Light tomorrow with today.", + "author": "Elizabeth Barrett Browning" + }, + { + "quote": "Forever is composed of nows.", + "author": "Emily Dickinson" + }, + { + "quote": "Computer science education cannot make anybody an expert programmer any more than studying brushes and pigment can make somebody an expert painter.", + "author": "Eric Raymond" + }, + { + "quote": "If you don't risk anything, you risk even more.", + "author": "Erica Jong" + }, + { + "quote": "The world breaks everyone, and afterward, many are strong at the broken places.", + "author": "Ernest Hemingway" + }, + { + "quote": "There is nothing noble in being superior to your fellow man; true nobility is being superior to your former self.", + "author": "Ernest Hemingway" + }, + { + "quote": "Never confuse a single defeat with a final defeat.", + "author": "F. Scott Fitzgerald" + }, + { + "quote": "I attribute my success to this - I never gave or took any excuse.", + "author": "Florence Nightingale" + }, + { + "quote": "The best revenge is massive success.", + "author": "Frank Sinatra" + }, + { + "quote": "The only limit to our realization of tomorrow, will be our doubts of today.", + "author": "Franklin D. Roosevelt" + }, + { + "quote": "Right or wrong, it's very pleasant to break something from time to time.", + "author": "Fyodor Dostoevsky" + }, + { + "quote": "The harder I work, the luckier I get.", + "author": "Gary Player" + }, + { + "quote": "Giving up is the only sure way to fail.", + "author": "Gena Showalter" + }, + { + "quote": "The only truly secure system is one that is powered off, cast in a block of concrete and sealed in a lead-lined room with armed guards.", + "author": "Gene Spafford" + }, + { + "quote": "A life spent making mistakes is not only more honorable, but more useful than a life spent doing nothing.", + "author": "George Bernard Shaw" + }, + { + "quote": "First learn computer science and all the theory. Next develop a programming style. Then forget all that and just hack.", + "author": "George Carrette" + }, + { + "quote": "Discovering the unexpected is more important than confirming the known.", + "author": "George Box" + }, + { + "quote": "We only see what we know.", + "author": "Goethe" + }, + { + "quote": "Without hard work, nothing grows but weeds.", + "author": "Gordon B. Hinckley" + }, + { + "quote": "The function of good software is to make the complex appear to be simple.", + "author": "Grady Booch" + }, + { + "quote": "When you know that you're capable of dealing with whatever comes, you have the only security the world has to offer.", + "author": "Harry Browne" + }, + { + "quote": "Pain is inevitable. Suffering is optional.", + "author": "Haruki Murakami" + }, + { + "quote": "Optimism is the faith that leads to achievement. Nothing can be done without hope and confidence.", + "author": "Helen Keller" + }, + { + "quote": "The price of anything is the amount of life you exchange for it.", + "author": "Henry David Thoreau" + }, + { + "quote": "Whether you think you can or think you can't, you're right.", + "author": "Henry Ford" + }, + { + "quote": "The most exciting phrase to hear in science, the one that heralds discoveries, is not 'Eureka!' but 'Now that's funny…'", + "author": "Isaac Asimov" + }, + { + "quote": "We are all failures. At least the best of us are.", + "author": "J.M. Barrie" + }, + { + "quote": "You can't wait for inspiration. You have to go after it with a club.", + "author": "Jack London" + }, + { + "quote": "Don't wish it were easier, wish you were better.", + "author": "Jim Rohn" + }, + { + "quote": "By seeking and blundering we learn.", + "author": "Johann Wolfgang von Goethe" + }, + { + "quote": "Knowing is not enough; we must apply. Wishing is not enough; we must do.", + "author": "Johann Wolfgang von Goethe" + }, + { + "quote": "We first make our habits, then our habits make us.", + "author": "John Dryden" + }, + { + "quote": "The power of imagination makes us infinite.", + "author": "John Muir" + }, + { + "quote": "May you live every day of your life.", + "author": "Jonathan Swift" + }, + { + "quote": "Perseverance is failing 19 times and succeeding the 20th.", + "author": "Julie Andrews" + }, + { + "quote": "The work of today is the history of tomorrow, and we are its makers.", + "author": "Juliette Gordon Low" + }, + { + "quote": "If you reveal your secrets to the wind, you should not blame the wind for revealing them to the trees.", + "author": "Kahlil Gibran" + }, + { + "quote": "Optimism is an occupational hazard of programming; feedback is the treatment.", + "author": "Kent Beck" + }, + { + "quote": "Opportunity does not knock, it presents itself when you beat down the door.", + "author": "Kyle Chandler" + }, + { + "quote": "To iterate is human, to recurse divine.", + "author": "Peter Deutsch" + }, + { + "quote": "A good traveler has no fixed plans and is not intent on arriving.", + "author": "Lao Tzu" + }, + { + "quote": "An ant on the move does more than a dozing ox.", + "author": "Lao Tzu" + }, + { + "quote": "Do the difficult things while they are easy and do the great things while they are small. A journey of a thousand miles must begin with a single step.", + "author": "Lao Tzu" + }, + { + "quote": "That's the thing about people who think they hate computers. What they really hate is lousy programmers.", + "author": "Larry Niven" + }, + { + "quote": "It had long since come to my attention that people of accomplishment rarely sat back and let things happen to them. They went out and happened to things.", + "author": "Leonardo da Vinci" + }, + { + "quote": "If you're any good at all, you know you can be better.", + "author": "Lindsay Buckingham" + }, + { + "quote": "If people never did silly things, nothing intelligent would ever get done.", + "author": "Ludwig Wittgenstein" + }, + { + "quote": "You only live once, but if you do it right, once is enough.", + "author": "Mae West" + }, + { + "quote": "Live as if you were to die tomorrow. Learn as if you were to live forever.", + "author": "Mahatma Gandhi" + }, + { + "quote": "Strength does not come from physical capacity. It comes from an indomitable will.", + "author": "Mahatma Gandhi" + }, + { + "quote": "One person's 'paranoia' is another person's 'engineering redundancy'.", + "author": "Marcus J. Ranum" + }, + { + "quote": "Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less.", + "author": "Marie Curie" + }, + { + "quote": "If you have everything under control, you're not moving fast enough.", + "author": "Mario Andretti" + }, + { + "quote": "Education: the path from cocky ignorance to miserable uncertainty.", + "author": "Mark Twain" + }, + { + "quote": "It ain't what you don't know that gets you into trouble. It's what you know for sure that just ain't so.", + "author": "Mark Twain" + }, + { + "quote": "The secret of getting ahead is getting started.", + "author": "Mark Twain" + }, + { + "quote": "The two most important days in your life are the day you are born and the day you find out why.", + "author": "Mark Twain" + }, + { + "quote": "Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails.", + "author": "Mark Twain" + }, + { + "quote": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", + "author": "Martin Fowler" + }, + { + "quote": "I know, somehow, that only when it is dark enough can you see the stars.", + "author": "Martin Luther King Jr." + }, + { + "quote": "It is never too late to be what you might have been.", + "author": "Mary Anne Evans" + }, + { + "quote": "Nothing will work unless you do.", + "author": "Maya Angelou" + }, + { + "quote": "We delight in the beauty of the butterfly, but rarely admit the changes it has gone through to achieve that beauty.", + "author": "Maya Angelou" + }, + { + "quote": "We may encounter many defeats, but we must not be defeated.", + "author": "Maya Angelou" + }, + { + "quote": "Everybody has talent, but ability takes hard work.", + "author": "Michael Jordan" + }, + { + "quote": "I've missed more than 9,000 shots during my career. I've lost almost 300 games. 26 times, I've been trusted to take the game winning shot and missed. I've failed over and over and over again in my life. And that is why I succeed.", + "author": "Michael Jordan" + }, + { + "quote": "Impossible is just a big word thrown around by small men who find it easier to live in the world they've been given than to explore the power they have to change it. Impossible is not a fact. It's an opinion. Impossible is not a declaration. It's a dare. Impossible is potential. Impossible is temporary. Impossible is nothing.", + "author": "Muhammad Ali" + }, + { + "quote": "A winner is a dreamer who never gives up.", + "author": "Nelson Mandela" + }, + { + "quote": "It always seems impossible until it's done.", + "author": "Nelson Mandela" + }, + { + "quote": "Failure will never overtake me if my determination to succeed is strong enough.", + "author": "Og Mandino" + }, + { + "quote": "I am not young enough to know everything.", + "author": "Oscar Wilde" + }, + { + "quote": "There is only one thing that makes a dream impossible to achieve: the fear of failure.", + "author": "Paulo Coelho" + }, + { + "quote": "Never go to bed mad. Stay up and fight.", + "author": "Phyllis Diller" + }, + { + "quote": "You can't cross the sea merely by standing and staring at the water.", + "author": "Rabindranath Tagore" + }, + { + "quote": "The only person you are destined to become is the person you decide to be.", + "author": "Ralph Waldo Emerson" + }, + { + "quote": "What you do speaks so loudly that I cannot hear what you say.", + "author": "Ralph Waldo Emerson" + }, + { + "quote": "People who are crazy enough to think they can change the world, are the ones who do.", + "author": "Rob Siltanen" + }, + { + "quote": "The best way out is always through.", + "author": "Robert Frost" + }, + { + "quote": "Today's accomplishments were yesterday's impossibilities.", + "author": "Robert H. Schuller" + }, + { + "quote": "Don't be satisfied with stories, how things have gone with others. Unfold your own myth.", + "author": "Rumi" + }, + { + "quote": "Forget safety. Live where you fear to live. Destroy your reputation. Be notorious.", + "author": "Rumi" + }, + { + "quote": "Sell your cleverness and buy bewilderment.", + "author": "Rumi" + }, + { + "quote": "The cure for pain is in the pain.", + "author": "Rumi" + }, + { + "quote": "Have no fear of perfection - you'll never reach it.", + "author": "Salvador Dalí" + }, + { + "quote": "Don't watch the clock. Do what it does. Keep going.", + "author": "Sam Levenson" + }, + { + "quote": "Ever Tried. Ever failed. No matter. Try again. Fail again. Fail better.", + "author": "Samuel Beckett" + }, + { + "quote": "The more you know, the more you realize you know nothing.", + "author": "Socrates" + }, + { + "quote": "The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.", + "author": "Stephen Hawking" + }, + { + "quote": "The universe doesn't allow perfection.", + "author": "Stephen Hawking" + }, + { + "quote": "Whether you want to uncover the secrets of the universe, or you want to pursue a career in the 21st century, basic computer programming is an essential skill to learn.", + "author": "Stephen Hawking" + }, + { + "quote": "The scariest moment is always just before you start.", + "author": "Stephen King" + }, + { + "quote": "You can, you should, and if you're brave enough to start, you will.", + "author": "Stephen King" + }, + { + "quote": "Arise, Awake and Stop not until the goal is reached.", + "author": "Swami Vivekananda" + }, + { + "quote": "It is said that your life flashes before your eyes just before you die. That is true, it's called Life.", + "author": "Terry Pratchett" + }, + { + "quote": "Believe you can and you're halfway there.", + "author": "Theodore Roosevelt" + }, + { + "quote": "I have not failed. I've just found 10,000 ways that won't work.", + "author": "Thomas A. Edison" + }, + { + "quote": "Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time.", + "author": "Thomas A. Edison" + }, + { + "quote": "The harder the conflict, the more glorious the triumph.", + "author": "Thomas Paine" + }, + { + "quote": "The Web as I envisaged it, we have not seen it yet. The future is still so much bigger than the past.", + "author": "Tim Berners-Lee" + }, + { + "quote": "Failure is the condiment that gives success its flavor.", + "author": "Truman Capote" + }, + { + "quote": "Those who says it cannot be done should not interrupt the person doing it.", + "author": "Unknown" + }, + { + "quote": "Even if you fall on your face, you're still moving forward.", + "author": "Victor Kiam" + }, + { + "quote": "It's not whether you get knocked down, it's whether you get up.", + "author": "Vince Lombardi" + }, + { + "quote": "I dream my painting and I paint my dream.", + "author": "Vincent van Gogh" + }, + { + "quote": "Let us cultivate our garden.", + "author": "Voltaire" + }, + { + "quote": "Aim for the moon. If you miss, you may hit a star.", + "author": "W. Clement Stone" + }, + { + "quote": "The way to get started is to quit talking and begin doing.", + "author": "Walt Disney" + }, + { + "quote": "You miss 100% of the shots you don't take.", + "author": "Wayne Gretzky" + }, + { + "quote": "Don't let yesterday take up too much of today.", + "author": "Will Rogers" + }, + { + "quote": "Even if you're on the right track, you'll get run over if you just sit there.", + "author": "Will Rogers" + }, + { + "quote": "Do not wait to strike till the iron is hot; but make it hot by striking.", + "author": "William Butler Yeats" + }, + { + "quote": "You cannot swim for new horizons until you have courage to lose sight of the shore.", + "author": "William Faulkner" + }, + { + "quote": "Be not afraid of greatness. Some are born great, some achieve greatness, and others have greatness thrust upon them.", + "author": "William Shakespeare" + }, + { + "quote": "We know what we are, but not what we may be.", + "author": "William Shakespeare" + }, + { + "quote": "In theory there is no difference between theory and practice. In practice there is.", + "author": "Yogi Berra" + }, + { + "quote": "You can see a lot by just looking.", + "author": "Yogi Berra" + }, + { + "quote": "There is no elevator to success, you have to take the stairs.", + "author": "Zig Ziglar" + }, + { + "quote": "You don't have to be great to start, but you have to start to be great.", + "author": "Zig Ziglar" + } + ] +} diff --git a/mobile-app-new/assets/learn/python.svg b/mobile-app-new/assets/learn/python.svg new file mode 100644 index 000000000..e3d0c1676 --- /dev/null +++ b/mobile-app-new/assets/learn/python.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/react.svg b/mobile-app-new/assets/learn/react.svg new file mode 100644 index 000000000..13d6c9be3 --- /dev/null +++ b/mobile-app-new/assets/learn/react.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/responsive-design.svg b/mobile-app-new/assets/learn/responsive-design.svg new file mode 100644 index 000000000..28ae632d3 --- /dev/null +++ b/mobile-app-new/assets/learn/responsive-design.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/rosetta-code.svg b/mobile-app-new/assets/learn/rosetta-code.svg new file mode 100644 index 000000000..2a45a1cfd --- /dev/null +++ b/mobile-app-new/assets/learn/rosetta-code.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/shield.svg b/mobile-app-new/assets/learn/shield.svg new file mode 100644 index 000000000..9d4c353d0 --- /dev/null +++ b/mobile-app-new/assets/learn/shield.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/tensorflow.svg b/mobile-app-new/assets/learn/tensorflow.svg new file mode 100644 index 000000000..244779346 --- /dev/null +++ b/mobile-app-new/assets/learn/tensorflow.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/assets/learn/viking-helmet.svg b/mobile-app-new/assets/learn/viking-helmet.svg new file mode 100644 index 000000000..c6f7f8866 --- /dev/null +++ b/mobile-app-new/assets/learn/viking-helmet.svg @@ -0,0 +1,3 @@ + diff --git a/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c179faa26..e1c79d9e1 100644 --- a/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5", + "version" : "1.2024072200.0" + } + }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "97f7d74dd0e8f3d0fe5fc75cc8957aa73bdc948a", + "version" : "11.3.2" + } + }, { "identity" : "auth0.swift", "kind" : "remoteSourceControl", @@ -9,6 +27,78 @@ "version" : "2.24.1" } }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "revision" : "27eaab3918e0bf78711cf1abf240577176326432", + "version" : "12.19.0" + } + }, + { + "identity" : "google-ads-on-device-conversion-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", + "state" : { + "revision" : "0208e2681ec81f8a9c81084696f60fcce26cb7ee", + "version" : "3.7.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "b8455dd2ff55cc269fda7a4f9eac6c1e3588c53f", + "version" : "12.19.2" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "ba3358d3c3dbae8ef230b58a46b97ad65e84e974", + "version" : "10.1.1" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "cc31f5ed464b111f877ce8554cbef4455d410d91", + "version" : "8.1.4" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6", + "version" : "1.69.1" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "724a52eea6329b7e12d3ad8300d76ca9f3895fcc", + "version" : "5.3.1" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe", + "version" : "101.0.0" + } + }, { "identity" : "jwtdecode.swift", "kind" : "remoteSourceControl", @@ -18,6 +108,33 @@ "version" : "3.3.0" } }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "3851d94a41890dea16dc3db34caf60e585cb4163", + "version" : "2.30910.1" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "f4a19a3c313dc2616c70bb49d29a799fb16be837", + "version" : "2.4.1" + } + }, { "identity" : "simplekeychain", "kind" : "remoteSourceControl", diff --git a/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved index c179faa26..e1c79d9e1 100644 --- a/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5", + "version" : "1.2024072200.0" + } + }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "97f7d74dd0e8f3d0fe5fc75cc8957aa73bdc948a", + "version" : "11.3.2" + } + }, { "identity" : "auth0.swift", "kind" : "remoteSourceControl", @@ -9,6 +27,78 @@ "version" : "2.24.1" } }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "revision" : "27eaab3918e0bf78711cf1abf240577176326432", + "version" : "12.19.0" + } + }, + { + "identity" : "google-ads-on-device-conversion-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", + "state" : { + "revision" : "0208e2681ec81f8a9c81084696f60fcce26cb7ee", + "version" : "3.7.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "b8455dd2ff55cc269fda7a4f9eac6c1e3588c53f", + "version" : "12.19.2" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "ba3358d3c3dbae8ef230b58a46b97ad65e84e974", + "version" : "10.1.1" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "cc31f5ed464b111f877ce8554cbef4455d410d91", + "version" : "8.1.4" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6", + "version" : "1.69.1" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "724a52eea6329b7e12d3ad8300d76ca9f3895fcc", + "version" : "5.3.1" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe", + "version" : "101.0.0" + } + }, { "identity" : "jwtdecode.swift", "kind" : "remoteSourceControl", @@ -18,6 +108,33 @@ "version" : "3.3.0" } }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "3851d94a41890dea16dc3db34caf60e585cb4163", + "version" : "2.30910.1" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "f4a19a3c313dc2616c70bb49d29a799fb16be837", + "version" : "2.4.1" + } + }, { "identity" : "simplekeychain", "kind" : "remoteSourceControl", diff --git a/mobile-app-new/lib/firebase_options.dart b/mobile-app-new/lib/firebase_options.dart new file mode 100644 index 000000000..c4e293da8 --- /dev/null +++ b/mobile-app-new/lib/firebase_options.dart @@ -0,0 +1,69 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyCP3yAWau0JJE8BPgsUrbT2K27EJX3z2KE', + appId: '1:363152234407:android:6293f9873ae6df8a738882', + messagingSenderId: '363152234407', + projectId: 'mobile-4ee8a', + storageBucket: 'mobile-4ee8a.firebasestorage.app', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyCFcTnIuxW7AeskmoNwbrEZX_ZvYz-aAdw', + appId: '1:363152234407:ios:17273b86fa3ff2e4738882', + messagingSenderId: '363152234407', + projectId: 'mobile-4ee8a', + storageBucket: 'mobile-4ee8a.firebasestorage.app', + iosBundleId: 'org.freecodecamp.ios', + ); + +} \ No newline at end of file diff --git a/mobile-app-new/lib/learn/constants/curriculum.dart b/mobile-app-new/lib/learn/constants/curriculum.dart new file mode 100644 index 000000000..88299112f --- /dev/null +++ b/mobile-app-new/lib/learn/constants/curriculum.dart @@ -0,0 +1,62 @@ +const chapterBasedSuperBlocks = [ + 'responsive-web-design-v9', + 'javascript-v9', + 'a1-professional-spanish', + 'python-v9', +]; + +// NOTE: available-superblocks.json stages shown, in order. `legacy` is deliberately absent. +const stageTitles = { + 'core': 'Recommended curriculum (still in beta):', + 'english': 'Learn English for Developers:', + 'spanish': 'Learn Professional Spanish:', + 'chinese': 'Learn Professional Chinese:', + 'extra': 'Prepare for the developer interview job search:', + 'professional': 'Professional certifications:', +}; + +const hiddenExamChapters = [ + 'responsive-web-design-certification-exam', + 'javascript-certification-exam', + 'python-certification-exam', +]; + +// NOTE: Hidden unless DEVELOPMENT_MODE is on. +const devOnlyChapters = ['frontend-libraries']; + +const _assets = 'assets/learn'; +const defaultSuperBlockIcon = '$_assets/graduation.svg'; + +const superBlockIcons = { + 'responsive-web-design-v9': '$_assets/responsive-design.svg', + 'javascript-v9': '$_assets/javascript.svg', + 'front-end-development-libraries-v9': '$_assets/react.svg', + 'python-v9': '$_assets/python.svg', + 'relational-databases-v9': '$_assets/database.svg', + 'back-end-development-and-apis-v9': '$_assets/api.svg', + '2022/responsive-web-design': '$_assets/responsive-design.svg', + 'responsive-web-design': '$_assets/responsive-design.svg', + 'javascript-algorithms-and-data-structures': '$_assets/javascript.svg', + 'javascript-algorithms-and-data-structures-v8': '$_assets/javascript.svg', + 'front-end-development-libraries': '$_assets/react.svg', + 'data-visualization': '$_assets/d3.svg', + 'back-end-development-and-apis': '$_assets/api.svg', + 'relational-database': '$_assets/database.svg', + 'quality-assurance': '$_assets/clipboard.svg', + 'scientific-computing-with-python': '$_assets/python.svg', + 'data-analysis-with-python': '$_assets/analytics.svg', + 'information-security': '$_assets/shield.svg', + 'machine-learning-with-python': '$_assets/tensorflow.svg', + 'coding-interview-prep': '$_assets/algorithm.svg', + 'the-odin-project': '$_assets/viking-helmet.svg', + 'project-euler': '$_assets/graduation.svg', + 'college-algebra-with-python': '$_assets/college-algebra.svg', + 'foundational-c-sharp-with-microsoft': '$_assets/c-sharp.svg', + 'a2-english-for-developers': '$_assets/a2-english.svg', + 'b1-english-for-developers': '$_assets/b1-english.svg', + 'a1-professional-spanish': '$_assets/a1-spanish.svg', + 'a1-professional-chinese': '$_assets/a1-chinese.svg', + 'rosetta-code': '$_assets/rosetta-code.svg', + 'python-for-everybody': '$_assets/python.svg', + 'dev-playground': '$_assets/code.svg', +}; diff --git a/mobile-app-new/lib/learn/controllers/curriculum_controller.dart b/mobile-app-new/lib/learn/controllers/curriculum_controller.dart new file mode 100644 index 000000000..8323216cf --- /dev/null +++ b/mobile-app-new/lib/learn/controllers/curriculum_controller.dart @@ -0,0 +1,22 @@ +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/services/curriculum_service.dart'; +import 'package:mobile_app_new/services/remote_config_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'curriculum_controller.g.dart'; + +// NOTE: Shared by the landing list and the superblock screen, which takes its title from here. +@riverpod +Future> curriculumStages(Ref ref) async { + final remoteConfig = await ref.watch(remoteConfigProvider.future); + return ref.watch(learnCurriculumServiceProvider).getStages(remoteConfig); +} + +// NOTE: Shared by the superblock screen and the module screen beneath it. +@riverpod +Future superBlock(Ref ref, String dashedName) async { + final remoteConfig = await ref.watch(remoteConfigProvider.future); + return ref + .watch(learnCurriculumServiceProvider) + .getSuperBlock(dashedName, remoteConfig); +} diff --git a/mobile-app-new/lib/learn/controllers/curriculum_controller.g.dart b/mobile-app-new/lib/learn/controllers/curriculum_controller.g.dart new file mode 100644 index 000000000..41f07e031 --- /dev/null +++ b/mobile-app-new/lib/learn/controllers/curriculum_controller.g.dart @@ -0,0 +1,125 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'curriculum_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(curriculumStages) +final curriculumStagesProvider = CurriculumStagesProvider._(); + +final class CurriculumStagesProvider + extends + $FunctionalProvider< + AsyncValue>, + List, + FutureOr> + > + with + $FutureModifier>, + $FutureProvider> { + CurriculumStagesProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'curriculumStagesProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$curriculumStagesHash(); + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + return curriculumStages(ref); + } +} + +String _$curriculumStagesHash() => r'838f632062a1e4199ad902508de54dd207f91873'; + +@ProviderFor(superBlock) +final superBlockProvider = SuperBlockFamily._(); + +final class SuperBlockProvider + extends + $FunctionalProvider< + AsyncValue, + SuperBlock, + FutureOr + > + with $FutureModifier, $FutureProvider { + SuperBlockProvider._({ + required SuperBlockFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'superBlockProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$superBlockHash(); + + @override + String toString() { + return r'superBlockProvider' + '' + '($argument)'; + } + + @$internal + @override + $FutureProviderElement $createElement($ProviderPointer pointer) => + $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as String; + return superBlock(ref, argument); + } + + @override + bool operator ==(Object other) { + return other is SuperBlockProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$superBlockHash() => r'5fe767b8aaafb0c3a25b05ea14d570f183997d97'; + +final class SuperBlockFamily extends $Family + with $FunctionalFamilyOverride, String> { + SuperBlockFamily._() + : super( + retry: null, + name: r'superBlockProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + SuperBlockProvider call(String dashedName) => + SuperBlockProvider._(argument: dashedName, from: this); + + @override + String toString() => r'superBlockProvider'; +} diff --git a/mobile-app-new/lib/learn/models/curriculum_model.dart b/mobile-app-new/lib/learn/models/curriculum_model.dart new file mode 100644 index 000000000..b74a3c7fc --- /dev/null +++ b/mobile-app-new/lib/learn/models/curriculum_model.dart @@ -0,0 +1,121 @@ +import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'curriculum_model.freezed.dart'; +part 'curriculum_model.g.dart'; + +enum BlockLayout { + challengeList, + challengeGrid, + challengeDialogue, + challengeLink; + + static BlockLayout fromValue(Object? value) { + switch (value) { + case 'project-list': + case 'challenge-list': + case 'legacy-challenge-list': + return challengeList; + case 'dialogue-grid': + return challengeDialogue; + case 'link': + case 'legacy-link': + return challengeLink; + default: + return challengeGrid; + } + } +} + +enum BlockLabel { + lecture('lecture'), + workshop('workshop'), + lab('lab'), + review('review'), + quiz('quiz'), + exam('exam'), + warmup('warm-up'), + learn('learn'), + practice('practice'), + legacy('legacy'); + + const BlockLabel(this.value); + + final String value; + + static BlockLabel fromValue(Object? value) => + values.firstWhereOrNull((label) => label.value == value) ?? legacy; +} + +enum ModuleType { + review('review'), + exam('exam'), + certProject('cert-project'); + + const ModuleType(this.value); + + final String value; + + static ModuleType? fromValue(Object? value) => + values.firstWhereOrNull((type) => type.value == value); +} + +@freezed +abstract class SuperBlockSummary with _$SuperBlockSummary { + const factory SuperBlockSummary({ + required String dashedName, + required String title, + required bool isPublic, + @Default(false) bool isDisabledByOverride, + }) = _SuperBlockSummary; +} + +@freezed +abstract class ChallengeOrder with _$ChallengeOrder { + const factory ChallengeOrder({required String id, required String title}) = + _ChallengeOrder; + + factory ChallengeOrder.fromJson(Map json) => + _$ChallengeOrderFromJson(json); +} + +@freezed +abstract class Block with _$Block { + const factory Block({ + required String superBlock, + required String dashedName, + required String name, + required BlockLayout layout, + required BlockLabel label, + required List description, + required List challenges, + @Default(false) bool isDisabledByOverride, + }) = _Block; +} + +@freezed +abstract class Module with _$Module { + const factory Module({ + required String dashedName, + required String name, + @Default(false) bool comingSoon, + ModuleType? moduleType, + required List blocks, + }) = _Module; +} + +@freezed +abstract class Chapter with _$Chapter { + const factory Chapter({ + required String dashedName, + required String name, + @Default(false) bool comingSoon, + required List modules, + }) = _Chapter; +} + +@freezed +sealed class SuperBlock with _$SuperBlock { + const factory SuperBlock.blocks(List blocks) = BlockSuperBlock; + const factory SuperBlock.chapters(List chapters) = ChapterSuperBlock; +} diff --git a/mobile-app-new/lib/learn/models/curriculum_model.freezed.dart b/mobile-app-new/lib/learn/models/curriculum_model.freezed.dart new file mode 100644 index 000000000..4e0944b50 --- /dev/null +++ b/mobile-app-new/lib/learn/models/curriculum_model.freezed.dart @@ -0,0 +1,1687 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'curriculum_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$SuperBlockSummary { + + String get dashedName; String get title; bool get isPublic; bool get isDisabledByOverride; +/// Create a copy of SuperBlockSummary +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SuperBlockSummaryCopyWith get copyWith => _$SuperBlockSummaryCopyWithImpl(this as SuperBlockSummary, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SuperBlockSummary&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.title, title) || other.title == title)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isDisabledByOverride, isDisabledByOverride) || other.isDisabledByOverride == isDisabledByOverride)); +} + + +@override +int get hashCode => Object.hash(runtimeType,dashedName,title,isPublic,isDisabledByOverride); + +@override +String toString() { + return 'SuperBlockSummary(dashedName: $dashedName, title: $title, isPublic: $isPublic, isDisabledByOverride: $isDisabledByOverride)'; +} + + +} + +/// @nodoc +abstract mixin class $SuperBlockSummaryCopyWith<$Res> { + factory $SuperBlockSummaryCopyWith(SuperBlockSummary value, $Res Function(SuperBlockSummary) _then) = _$SuperBlockSummaryCopyWithImpl; +@useResult +$Res call({ + String dashedName, String title, bool isPublic, bool isDisabledByOverride +}); + + + + +} +/// @nodoc +class _$SuperBlockSummaryCopyWithImpl<$Res> + implements $SuperBlockSummaryCopyWith<$Res> { + _$SuperBlockSummaryCopyWithImpl(this._self, this._then); + + final SuperBlockSummary _self; + final $Res Function(SuperBlockSummary) _then; + +/// Create a copy of SuperBlockSummary +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? dashedName = null,Object? title = null,Object? isPublic = null,Object? isDisabledByOverride = null,}) { + return _then(_self.copyWith( +dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable +as bool,isDisabledByOverride: null == isDisabledByOverride ? _self.isDisabledByOverride : isDisabledByOverride // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SuperBlockSummary]. +extension SuperBlockSummaryPatterns on SuperBlockSummary { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SuperBlockSummary value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SuperBlockSummary() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SuperBlockSummary value) $default,){ +final _that = this; +switch (_that) { +case _SuperBlockSummary(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SuperBlockSummary value)? $default,){ +final _that = this; +switch (_that) { +case _SuperBlockSummary() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String dashedName, String title, bool isPublic, bool isDisabledByOverride)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SuperBlockSummary() when $default != null: +return $default(_that.dashedName,_that.title,_that.isPublic,_that.isDisabledByOverride);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String dashedName, String title, bool isPublic, bool isDisabledByOverride) $default,) {final _that = this; +switch (_that) { +case _SuperBlockSummary(): +return $default(_that.dashedName,_that.title,_that.isPublic,_that.isDisabledByOverride);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String dashedName, String title, bool isPublic, bool isDisabledByOverride)? $default,) {final _that = this; +switch (_that) { +case _SuperBlockSummary() when $default != null: +return $default(_that.dashedName,_that.title,_that.isPublic,_that.isDisabledByOverride);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _SuperBlockSummary implements SuperBlockSummary { + const _SuperBlockSummary({required this.dashedName, required this.title, required this.isPublic, this.isDisabledByOverride = false}); + + +@override final String dashedName; +@override final String title; +@override final bool isPublic; +@override@JsonKey() final bool isDisabledByOverride; + +/// Create a copy of SuperBlockSummary +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SuperBlockSummaryCopyWith<_SuperBlockSummary> get copyWith => __$SuperBlockSummaryCopyWithImpl<_SuperBlockSummary>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SuperBlockSummary&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.title, title) || other.title == title)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isDisabledByOverride, isDisabledByOverride) || other.isDisabledByOverride == isDisabledByOverride)); +} + + +@override +int get hashCode => Object.hash(runtimeType,dashedName,title,isPublic,isDisabledByOverride); + +@override +String toString() { + return 'SuperBlockSummary(dashedName: $dashedName, title: $title, isPublic: $isPublic, isDisabledByOverride: $isDisabledByOverride)'; +} + + +} + +/// @nodoc +abstract mixin class _$SuperBlockSummaryCopyWith<$Res> implements $SuperBlockSummaryCopyWith<$Res> { + factory _$SuperBlockSummaryCopyWith(_SuperBlockSummary value, $Res Function(_SuperBlockSummary) _then) = __$SuperBlockSummaryCopyWithImpl; +@override @useResult +$Res call({ + String dashedName, String title, bool isPublic, bool isDisabledByOverride +}); + + + + +} +/// @nodoc +class __$SuperBlockSummaryCopyWithImpl<$Res> + implements _$SuperBlockSummaryCopyWith<$Res> { + __$SuperBlockSummaryCopyWithImpl(this._self, this._then); + + final _SuperBlockSummary _self; + final $Res Function(_SuperBlockSummary) _then; + +/// Create a copy of SuperBlockSummary +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? dashedName = null,Object? title = null,Object? isPublic = null,Object? isDisabledByOverride = null,}) { + return _then(_SuperBlockSummary( +dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable +as bool,isDisabledByOverride: null == isDisabledByOverride ? _self.isDisabledByOverride : isDisabledByOverride // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + + +/// @nodoc +mixin _$ChallengeOrder { + + String get id; String get title; +/// Create a copy of ChallengeOrder +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ChallengeOrderCopyWith get copyWith => _$ChallengeOrderCopyWithImpl(this as ChallengeOrder, _$identity); + + /// Serializes this ChallengeOrder to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ChallengeOrder&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,title); + +@override +String toString() { + return 'ChallengeOrder(id: $id, title: $title)'; +} + + +} + +/// @nodoc +abstract mixin class $ChallengeOrderCopyWith<$Res> { + factory $ChallengeOrderCopyWith(ChallengeOrder value, $Res Function(ChallengeOrder) _then) = _$ChallengeOrderCopyWithImpl; +@useResult +$Res call({ + String id, String title +}); + + + + +} +/// @nodoc +class _$ChallengeOrderCopyWithImpl<$Res> + implements $ChallengeOrderCopyWith<$Res> { + _$ChallengeOrderCopyWithImpl(this._self, this._then); + + final ChallengeOrder _self; + final $Res Function(ChallengeOrder) _then; + +/// Create a copy of ChallengeOrder +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ChallengeOrder]. +extension ChallengeOrderPatterns on ChallengeOrder { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ChallengeOrder value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ChallengeOrder() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ChallengeOrder value) $default,){ +final _that = this; +switch (_that) { +case _ChallengeOrder(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ChallengeOrder value)? $default,){ +final _that = this; +switch (_that) { +case _ChallengeOrder() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String title)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ChallengeOrder() when $default != null: +return $default(_that.id,_that.title);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String title) $default,) {final _that = this; +switch (_that) { +case _ChallengeOrder(): +return $default(_that.id,_that.title);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String title)? $default,) {final _that = this; +switch (_that) { +case _ChallengeOrder() when $default != null: +return $default(_that.id,_that.title);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ChallengeOrder implements ChallengeOrder { + const _ChallengeOrder({required this.id, required this.title}); + factory _ChallengeOrder.fromJson(Map json) => _$ChallengeOrderFromJson(json); + +@override final String id; +@override final String title; + +/// Create a copy of ChallengeOrder +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ChallengeOrderCopyWith<_ChallengeOrder> get copyWith => __$ChallengeOrderCopyWithImpl<_ChallengeOrder>(this, _$identity); + +@override +Map toJson() { + return _$ChallengeOrderToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChallengeOrder&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,title); + +@override +String toString() { + return 'ChallengeOrder(id: $id, title: $title)'; +} + + +} + +/// @nodoc +abstract mixin class _$ChallengeOrderCopyWith<$Res> implements $ChallengeOrderCopyWith<$Res> { + factory _$ChallengeOrderCopyWith(_ChallengeOrder value, $Res Function(_ChallengeOrder) _then) = __$ChallengeOrderCopyWithImpl; +@override @useResult +$Res call({ + String id, String title +}); + + + + +} +/// @nodoc +class __$ChallengeOrderCopyWithImpl<$Res> + implements _$ChallengeOrderCopyWith<$Res> { + __$ChallengeOrderCopyWithImpl(this._self, this._then); + + final _ChallengeOrder _self; + final $Res Function(_ChallengeOrder) _then; + +/// Create a copy of ChallengeOrder +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,}) { + return _then(_ChallengeOrder( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc +mixin _$Block { + + String get superBlock; String get dashedName; String get name; BlockLayout get layout; BlockLabel get label; List get description; List get challenges; bool get isDisabledByOverride; +/// Create a copy of Block +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BlockCopyWith get copyWith => _$BlockCopyWithImpl(this as Block, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Block&&(identical(other.superBlock, superBlock) || other.superBlock == superBlock)&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.name, name) || other.name == name)&&(identical(other.layout, layout) || other.layout == layout)&&(identical(other.label, label) || other.label == label)&&const DeepCollectionEquality().equals(other.description, description)&&const DeepCollectionEquality().equals(other.challenges, challenges)&&(identical(other.isDisabledByOverride, isDisabledByOverride) || other.isDisabledByOverride == isDisabledByOverride)); +} + + +@override +int get hashCode => Object.hash(runtimeType,superBlock,dashedName,name,layout,label,const DeepCollectionEquality().hash(description),const DeepCollectionEquality().hash(challenges),isDisabledByOverride); + +@override +String toString() { + return 'Block(superBlock: $superBlock, dashedName: $dashedName, name: $name, layout: $layout, label: $label, description: $description, challenges: $challenges, isDisabledByOverride: $isDisabledByOverride)'; +} + + +} + +/// @nodoc +abstract mixin class $BlockCopyWith<$Res> { + factory $BlockCopyWith(Block value, $Res Function(Block) _then) = _$BlockCopyWithImpl; +@useResult +$Res call({ + String superBlock, String dashedName, String name, BlockLayout layout, BlockLabel label, List description, List challenges, bool isDisabledByOverride +}); + + + + +} +/// @nodoc +class _$BlockCopyWithImpl<$Res> + implements $BlockCopyWith<$Res> { + _$BlockCopyWithImpl(this._self, this._then); + + final Block _self; + final $Res Function(Block) _then; + +/// Create a copy of Block +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? superBlock = null,Object? dashedName = null,Object? name = null,Object? layout = null,Object? label = null,Object? description = null,Object? challenges = null,Object? isDisabledByOverride = null,}) { + return _then(_self.copyWith( +superBlock: null == superBlock ? _self.superBlock : superBlock // ignore: cast_nullable_to_non_nullable +as String,dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,layout: null == layout ? _self.layout : layout // ignore: cast_nullable_to_non_nullable +as BlockLayout,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable +as BlockLabel,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as List,challenges: null == challenges ? _self.challenges : challenges // ignore: cast_nullable_to_non_nullable +as List,isDisabledByOverride: null == isDisabledByOverride ? _self.isDisabledByOverride : isDisabledByOverride // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Block]. +extension BlockPatterns on Block { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Block value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Block() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Block value) $default,){ +final _that = this; +switch (_that) { +case _Block(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Block value)? $default,){ +final _that = this; +switch (_that) { +case _Block() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String superBlock, String dashedName, String name, BlockLayout layout, BlockLabel label, List description, List challenges, bool isDisabledByOverride)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Block() when $default != null: +return $default(_that.superBlock,_that.dashedName,_that.name,_that.layout,_that.label,_that.description,_that.challenges,_that.isDisabledByOverride);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String superBlock, String dashedName, String name, BlockLayout layout, BlockLabel label, List description, List challenges, bool isDisabledByOverride) $default,) {final _that = this; +switch (_that) { +case _Block(): +return $default(_that.superBlock,_that.dashedName,_that.name,_that.layout,_that.label,_that.description,_that.challenges,_that.isDisabledByOverride);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String superBlock, String dashedName, String name, BlockLayout layout, BlockLabel label, List description, List challenges, bool isDisabledByOverride)? $default,) {final _that = this; +switch (_that) { +case _Block() when $default != null: +return $default(_that.superBlock,_that.dashedName,_that.name,_that.layout,_that.label,_that.description,_that.challenges,_that.isDisabledByOverride);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _Block implements Block { + const _Block({required this.superBlock, required this.dashedName, required this.name, required this.layout, required this.label, required final List description, required final List challenges, this.isDisabledByOverride = false}): _description = description,_challenges = challenges; + + +@override final String superBlock; +@override final String dashedName; +@override final String name; +@override final BlockLayout layout; +@override final BlockLabel label; + final List _description; +@override List get description { + if (_description is EqualUnmodifiableListView) return _description; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_description); +} + + final List _challenges; +@override List get challenges { + if (_challenges is EqualUnmodifiableListView) return _challenges; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_challenges); +} + +@override@JsonKey() final bool isDisabledByOverride; + +/// Create a copy of Block +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BlockCopyWith<_Block> get copyWith => __$BlockCopyWithImpl<_Block>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Block&&(identical(other.superBlock, superBlock) || other.superBlock == superBlock)&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.name, name) || other.name == name)&&(identical(other.layout, layout) || other.layout == layout)&&(identical(other.label, label) || other.label == label)&&const DeepCollectionEquality().equals(other._description, _description)&&const DeepCollectionEquality().equals(other._challenges, _challenges)&&(identical(other.isDisabledByOverride, isDisabledByOverride) || other.isDisabledByOverride == isDisabledByOverride)); +} + + +@override +int get hashCode => Object.hash(runtimeType,superBlock,dashedName,name,layout,label,const DeepCollectionEquality().hash(_description),const DeepCollectionEquality().hash(_challenges),isDisabledByOverride); + +@override +String toString() { + return 'Block(superBlock: $superBlock, dashedName: $dashedName, name: $name, layout: $layout, label: $label, description: $description, challenges: $challenges, isDisabledByOverride: $isDisabledByOverride)'; +} + + +} + +/// @nodoc +abstract mixin class _$BlockCopyWith<$Res> implements $BlockCopyWith<$Res> { + factory _$BlockCopyWith(_Block value, $Res Function(_Block) _then) = __$BlockCopyWithImpl; +@override @useResult +$Res call({ + String superBlock, String dashedName, String name, BlockLayout layout, BlockLabel label, List description, List challenges, bool isDisabledByOverride +}); + + + + +} +/// @nodoc +class __$BlockCopyWithImpl<$Res> + implements _$BlockCopyWith<$Res> { + __$BlockCopyWithImpl(this._self, this._then); + + final _Block _self; + final $Res Function(_Block) _then; + +/// Create a copy of Block +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? superBlock = null,Object? dashedName = null,Object? name = null,Object? layout = null,Object? label = null,Object? description = null,Object? challenges = null,Object? isDisabledByOverride = null,}) { + return _then(_Block( +superBlock: null == superBlock ? _self.superBlock : superBlock // ignore: cast_nullable_to_non_nullable +as String,dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,layout: null == layout ? _self.layout : layout // ignore: cast_nullable_to_non_nullable +as BlockLayout,label: null == label ? _self.label : label // ignore: cast_nullable_to_non_nullable +as BlockLabel,description: null == description ? _self._description : description // ignore: cast_nullable_to_non_nullable +as List,challenges: null == challenges ? _self._challenges : challenges // ignore: cast_nullable_to_non_nullable +as List,isDisabledByOverride: null == isDisabledByOverride ? _self.isDisabledByOverride : isDisabledByOverride // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +/// @nodoc +mixin _$Module { + + String get dashedName; String get name; bool get comingSoon; ModuleType? get moduleType; List get blocks; +/// Create a copy of Module +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ModuleCopyWith get copyWith => _$ModuleCopyWithImpl(this as Module, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Module&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.name, name) || other.name == name)&&(identical(other.comingSoon, comingSoon) || other.comingSoon == comingSoon)&&(identical(other.moduleType, moduleType) || other.moduleType == moduleType)&&const DeepCollectionEquality().equals(other.blocks, blocks)); +} + + +@override +int get hashCode => Object.hash(runtimeType,dashedName,name,comingSoon,moduleType,const DeepCollectionEquality().hash(blocks)); + +@override +String toString() { + return 'Module(dashedName: $dashedName, name: $name, comingSoon: $comingSoon, moduleType: $moduleType, blocks: $blocks)'; +} + + +} + +/// @nodoc +abstract mixin class $ModuleCopyWith<$Res> { + factory $ModuleCopyWith(Module value, $Res Function(Module) _then) = _$ModuleCopyWithImpl; +@useResult +$Res call({ + String dashedName, String name, bool comingSoon, ModuleType? moduleType, List blocks +}); + + + + +} +/// @nodoc +class _$ModuleCopyWithImpl<$Res> + implements $ModuleCopyWith<$Res> { + _$ModuleCopyWithImpl(this._self, this._then); + + final Module _self; + final $Res Function(Module) _then; + +/// Create a copy of Module +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? dashedName = null,Object? name = null,Object? comingSoon = null,Object? moduleType = freezed,Object? blocks = null,}) { + return _then(_self.copyWith( +dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,comingSoon: null == comingSoon ? _self.comingSoon : comingSoon // ignore: cast_nullable_to_non_nullable +as bool,moduleType: freezed == moduleType ? _self.moduleType : moduleType // ignore: cast_nullable_to_non_nullable +as ModuleType?,blocks: null == blocks ? _self.blocks : blocks // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Module]. +extension ModulePatterns on Module { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Module value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Module() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Module value) $default,){ +final _that = this; +switch (_that) { +case _Module(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Module value)? $default,){ +final _that = this; +switch (_that) { +case _Module() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String dashedName, String name, bool comingSoon, ModuleType? moduleType, List blocks)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Module() when $default != null: +return $default(_that.dashedName,_that.name,_that.comingSoon,_that.moduleType,_that.blocks);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String dashedName, String name, bool comingSoon, ModuleType? moduleType, List blocks) $default,) {final _that = this; +switch (_that) { +case _Module(): +return $default(_that.dashedName,_that.name,_that.comingSoon,_that.moduleType,_that.blocks);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String dashedName, String name, bool comingSoon, ModuleType? moduleType, List blocks)? $default,) {final _that = this; +switch (_that) { +case _Module() when $default != null: +return $default(_that.dashedName,_that.name,_that.comingSoon,_that.moduleType,_that.blocks);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _Module implements Module { + const _Module({required this.dashedName, required this.name, this.comingSoon = false, this.moduleType, required final List blocks}): _blocks = blocks; + + +@override final String dashedName; +@override final String name; +@override@JsonKey() final bool comingSoon; +@override final ModuleType? moduleType; + final List _blocks; +@override List get blocks { + if (_blocks is EqualUnmodifiableListView) return _blocks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_blocks); +} + + +/// Create a copy of Module +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ModuleCopyWith<_Module> get copyWith => __$ModuleCopyWithImpl<_Module>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Module&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.name, name) || other.name == name)&&(identical(other.comingSoon, comingSoon) || other.comingSoon == comingSoon)&&(identical(other.moduleType, moduleType) || other.moduleType == moduleType)&&const DeepCollectionEquality().equals(other._blocks, _blocks)); +} + + +@override +int get hashCode => Object.hash(runtimeType,dashedName,name,comingSoon,moduleType,const DeepCollectionEquality().hash(_blocks)); + +@override +String toString() { + return 'Module(dashedName: $dashedName, name: $name, comingSoon: $comingSoon, moduleType: $moduleType, blocks: $blocks)'; +} + + +} + +/// @nodoc +abstract mixin class _$ModuleCopyWith<$Res> implements $ModuleCopyWith<$Res> { + factory _$ModuleCopyWith(_Module value, $Res Function(_Module) _then) = __$ModuleCopyWithImpl; +@override @useResult +$Res call({ + String dashedName, String name, bool comingSoon, ModuleType? moduleType, List blocks +}); + + + + +} +/// @nodoc +class __$ModuleCopyWithImpl<$Res> + implements _$ModuleCopyWith<$Res> { + __$ModuleCopyWithImpl(this._self, this._then); + + final _Module _self; + final $Res Function(_Module) _then; + +/// Create a copy of Module +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? dashedName = null,Object? name = null,Object? comingSoon = null,Object? moduleType = freezed,Object? blocks = null,}) { + return _then(_Module( +dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,comingSoon: null == comingSoon ? _self.comingSoon : comingSoon // ignore: cast_nullable_to_non_nullable +as bool,moduleType: freezed == moduleType ? _self.moduleType : moduleType // ignore: cast_nullable_to_non_nullable +as ModuleType?,blocks: null == blocks ? _self._blocks : blocks // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$Chapter { + + String get dashedName; String get name; bool get comingSoon; List get modules; +/// Create a copy of Chapter +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ChapterCopyWith get copyWith => _$ChapterCopyWithImpl(this as Chapter, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Chapter&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.name, name) || other.name == name)&&(identical(other.comingSoon, comingSoon) || other.comingSoon == comingSoon)&&const DeepCollectionEquality().equals(other.modules, modules)); +} + + +@override +int get hashCode => Object.hash(runtimeType,dashedName,name,comingSoon,const DeepCollectionEquality().hash(modules)); + +@override +String toString() { + return 'Chapter(dashedName: $dashedName, name: $name, comingSoon: $comingSoon, modules: $modules)'; +} + + +} + +/// @nodoc +abstract mixin class $ChapterCopyWith<$Res> { + factory $ChapterCopyWith(Chapter value, $Res Function(Chapter) _then) = _$ChapterCopyWithImpl; +@useResult +$Res call({ + String dashedName, String name, bool comingSoon, List modules +}); + + + + +} +/// @nodoc +class _$ChapterCopyWithImpl<$Res> + implements $ChapterCopyWith<$Res> { + _$ChapterCopyWithImpl(this._self, this._then); + + final Chapter _self; + final $Res Function(Chapter) _then; + +/// Create a copy of Chapter +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? dashedName = null,Object? name = null,Object? comingSoon = null,Object? modules = null,}) { + return _then(_self.copyWith( +dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,comingSoon: null == comingSoon ? _self.comingSoon : comingSoon // ignore: cast_nullable_to_non_nullable +as bool,modules: null == modules ? _self.modules : modules // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Chapter]. +extension ChapterPatterns on Chapter { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Chapter value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Chapter() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Chapter value) $default,){ +final _that = this; +switch (_that) { +case _Chapter(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Chapter value)? $default,){ +final _that = this; +switch (_that) { +case _Chapter() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String dashedName, String name, bool comingSoon, List modules)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Chapter() when $default != null: +return $default(_that.dashedName,_that.name,_that.comingSoon,_that.modules);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String dashedName, String name, bool comingSoon, List modules) $default,) {final _that = this; +switch (_that) { +case _Chapter(): +return $default(_that.dashedName,_that.name,_that.comingSoon,_that.modules);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String dashedName, String name, bool comingSoon, List modules)? $default,) {final _that = this; +switch (_that) { +case _Chapter() when $default != null: +return $default(_that.dashedName,_that.name,_that.comingSoon,_that.modules);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _Chapter implements Chapter { + const _Chapter({required this.dashedName, required this.name, this.comingSoon = false, required final List modules}): _modules = modules; + + +@override final String dashedName; +@override final String name; +@override@JsonKey() final bool comingSoon; + final List _modules; +@override List get modules { + if (_modules is EqualUnmodifiableListView) return _modules; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_modules); +} + + +/// Create a copy of Chapter +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ChapterCopyWith<_Chapter> get copyWith => __$ChapterCopyWithImpl<_Chapter>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Chapter&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.name, name) || other.name == name)&&(identical(other.comingSoon, comingSoon) || other.comingSoon == comingSoon)&&const DeepCollectionEquality().equals(other._modules, _modules)); +} + + +@override +int get hashCode => Object.hash(runtimeType,dashedName,name,comingSoon,const DeepCollectionEquality().hash(_modules)); + +@override +String toString() { + return 'Chapter(dashedName: $dashedName, name: $name, comingSoon: $comingSoon, modules: $modules)'; +} + + +} + +/// @nodoc +abstract mixin class _$ChapterCopyWith<$Res> implements $ChapterCopyWith<$Res> { + factory _$ChapterCopyWith(_Chapter value, $Res Function(_Chapter) _then) = __$ChapterCopyWithImpl; +@override @useResult +$Res call({ + String dashedName, String name, bool comingSoon, List modules +}); + + + + +} +/// @nodoc +class __$ChapterCopyWithImpl<$Res> + implements _$ChapterCopyWith<$Res> { + __$ChapterCopyWithImpl(this._self, this._then); + + final _Chapter _self; + final $Res Function(_Chapter) _then; + +/// Create a copy of Chapter +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? dashedName = null,Object? name = null,Object? comingSoon = null,Object? modules = null,}) { + return _then(_Chapter( +dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,comingSoon: null == comingSoon ? _self.comingSoon : comingSoon // ignore: cast_nullable_to_non_nullable +as bool,modules: null == modules ? _self._modules : modules // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$SuperBlock { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SuperBlock); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'SuperBlock()'; +} + + +} + +/// @nodoc +class $SuperBlockCopyWith<$Res> { +$SuperBlockCopyWith(SuperBlock _, $Res Function(SuperBlock) __); +} + + +/// Adds pattern-matching-related methods to [SuperBlock]. +extension SuperBlockPatterns on SuperBlock { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( BlockSuperBlock value)? blocks,TResult Function( ChapterSuperBlock value)? chapters,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case BlockSuperBlock() when blocks != null: +return blocks(_that);case ChapterSuperBlock() when chapters != null: +return chapters(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( BlockSuperBlock value) blocks,required TResult Function( ChapterSuperBlock value) chapters,}){ +final _that = this; +switch (_that) { +case BlockSuperBlock(): +return blocks(_that);case ChapterSuperBlock(): +return chapters(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( BlockSuperBlock value)? blocks,TResult? Function( ChapterSuperBlock value)? chapters,}){ +final _that = this; +switch (_that) { +case BlockSuperBlock() when blocks != null: +return blocks(_that);case ChapterSuperBlock() when chapters != null: +return chapters(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( List blocks)? blocks,TResult Function( List chapters)? chapters,required TResult orElse(),}) {final _that = this; +switch (_that) { +case BlockSuperBlock() when blocks != null: +return blocks(_that.blocks);case ChapterSuperBlock() when chapters != null: +return chapters(_that.chapters);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( List blocks) blocks,required TResult Function( List chapters) chapters,}) {final _that = this; +switch (_that) { +case BlockSuperBlock(): +return blocks(_that.blocks);case ChapterSuperBlock(): +return chapters(_that.chapters);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( List blocks)? blocks,TResult? Function( List chapters)? chapters,}) {final _that = this; +switch (_that) { +case BlockSuperBlock() when blocks != null: +return blocks(_that.blocks);case ChapterSuperBlock() when chapters != null: +return chapters(_that.chapters);case _: + return null; + +} +} + +} + +/// @nodoc + + +class BlockSuperBlock implements SuperBlock { + const BlockSuperBlock(final List blocks): _blocks = blocks; + + + final List _blocks; + List get blocks { + if (_blocks is EqualUnmodifiableListView) return _blocks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_blocks); +} + + +/// Create a copy of SuperBlock +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BlockSuperBlockCopyWith get copyWith => _$BlockSuperBlockCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is BlockSuperBlock&&const DeepCollectionEquality().equals(other._blocks, _blocks)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_blocks)); + +@override +String toString() { + return 'SuperBlock.blocks(blocks: $blocks)'; +} + + +} + +/// @nodoc +abstract mixin class $BlockSuperBlockCopyWith<$Res> implements $SuperBlockCopyWith<$Res> { + factory $BlockSuperBlockCopyWith(BlockSuperBlock value, $Res Function(BlockSuperBlock) _then) = _$BlockSuperBlockCopyWithImpl; +@useResult +$Res call({ + List blocks +}); + + + + +} +/// @nodoc +class _$BlockSuperBlockCopyWithImpl<$Res> + implements $BlockSuperBlockCopyWith<$Res> { + _$BlockSuperBlockCopyWithImpl(this._self, this._then); + + final BlockSuperBlock _self; + final $Res Function(BlockSuperBlock) _then; + +/// Create a copy of SuperBlock +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? blocks = null,}) { + return _then(BlockSuperBlock( +null == blocks ? _self._blocks : blocks // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc + + +class ChapterSuperBlock implements SuperBlock { + const ChapterSuperBlock(final List chapters): _chapters = chapters; + + + final List _chapters; + List get chapters { + if (_chapters is EqualUnmodifiableListView) return _chapters; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_chapters); +} + + +/// Create a copy of SuperBlock +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ChapterSuperBlockCopyWith get copyWith => _$ChapterSuperBlockCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ChapterSuperBlock&&const DeepCollectionEquality().equals(other._chapters, _chapters)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_chapters)); + +@override +String toString() { + return 'SuperBlock.chapters(chapters: $chapters)'; +} + + +} + +/// @nodoc +abstract mixin class $ChapterSuperBlockCopyWith<$Res> implements $SuperBlockCopyWith<$Res> { + factory $ChapterSuperBlockCopyWith(ChapterSuperBlock value, $Res Function(ChapterSuperBlock) _then) = _$ChapterSuperBlockCopyWithImpl; +@useResult +$Res call({ + List chapters +}); + + + + +} +/// @nodoc +class _$ChapterSuperBlockCopyWithImpl<$Res> + implements $ChapterSuperBlockCopyWith<$Res> { + _$ChapterSuperBlockCopyWithImpl(this._self, this._then); + + final ChapterSuperBlock _self; + final $Res Function(ChapterSuperBlock) _then; + +/// Create a copy of SuperBlock +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? chapters = null,}) { + return _then(ChapterSuperBlock( +null == chapters ? _self._chapters : chapters // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/learn/models/curriculum_model.g.dart b/mobile-app-new/lib/learn/models/curriculum_model.g.dart new file mode 100644 index 000000000..06a60d4b9 --- /dev/null +++ b/mobile-app-new/lib/learn/models/curriculum_model.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'curriculum_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_ChallengeOrder _$ChallengeOrderFromJson(Map json) => + _ChallengeOrder(id: json['id'] as String, title: json['title'] as String); + +Map _$ChallengeOrderToJson(_ChallengeOrder instance) => + {'id': instance.id, 'title': instance.title}; diff --git a/mobile-app-new/lib/learn/models/daily_challenge_model.dart b/mobile-app-new/lib/learn/models/daily_challenge_model.dart new file mode 100644 index 000000000..a09a60ef1 --- /dev/null +++ b/mobile-app-new/lib/learn/models/daily_challenge_model.dart @@ -0,0 +1,22 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'daily_challenge_model.freezed.dart'; +part 'daily_challenge_model.g.dart'; + +@freezed +abstract class DailyChallengeOverview with _$DailyChallengeOverview { + const factory DailyChallengeOverview({ + required String id, + required int challengeNumber, + required DateTime date, + required String title, + }) = _DailyChallengeOverview; + + const DailyChallengeOverview._(); + + // NOTE: MM-DD. The 365 challenges repeat every year, so the year in `date` means nothing. + String get monthDay => date.toIso8601String().substring(5, 10); + + factory DailyChallengeOverview.fromJson(Map json) => + _$DailyChallengeOverviewFromJson(json); +} diff --git a/mobile-app-new/lib/learn/models/daily_challenge_model.freezed.dart b/mobile-app-new/lib/learn/models/daily_challenge_model.freezed.dart new file mode 100644 index 000000000..0515f2c5a --- /dev/null +++ b/mobile-app-new/lib/learn/models/daily_challenge_model.freezed.dart @@ -0,0 +1,286 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'daily_challenge_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$DailyChallengeOverview { + + String get id; int get challengeNumber; DateTime get date; String get title; +/// Create a copy of DailyChallengeOverview +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$DailyChallengeOverviewCopyWith get copyWith => _$DailyChallengeOverviewCopyWithImpl(this as DailyChallengeOverview, _$identity); + + /// Serializes this DailyChallengeOverview to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is DailyChallengeOverview&&(identical(other.id, id) || other.id == id)&&(identical(other.challengeNumber, challengeNumber) || other.challengeNumber == challengeNumber)&&(identical(other.date, date) || other.date == date)&&(identical(other.title, title) || other.title == title)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,challengeNumber,date,title); + +@override +String toString() { + return 'DailyChallengeOverview(id: $id, challengeNumber: $challengeNumber, date: $date, title: $title)'; +} + + +} + +/// @nodoc +abstract mixin class $DailyChallengeOverviewCopyWith<$Res> { + factory $DailyChallengeOverviewCopyWith(DailyChallengeOverview value, $Res Function(DailyChallengeOverview) _then) = _$DailyChallengeOverviewCopyWithImpl; +@useResult +$Res call({ + String id, int challengeNumber, DateTime date, String title +}); + + + + +} +/// @nodoc +class _$DailyChallengeOverviewCopyWithImpl<$Res> + implements $DailyChallengeOverviewCopyWith<$Res> { + _$DailyChallengeOverviewCopyWithImpl(this._self, this._then); + + final DailyChallengeOverview _self; + final $Res Function(DailyChallengeOverview) _then; + +/// Create a copy of DailyChallengeOverview +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? challengeNumber = null,Object? date = null,Object? title = null,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,challengeNumber: null == challengeNumber ? _self.challengeNumber : challengeNumber // ignore: cast_nullable_to_non_nullable +as int,date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable +as DateTime,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [DailyChallengeOverview]. +extension DailyChallengeOverviewPatterns on DailyChallengeOverview { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _DailyChallengeOverview value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _DailyChallengeOverview() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _DailyChallengeOverview value) $default,){ +final _that = this; +switch (_that) { +case _DailyChallengeOverview(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _DailyChallengeOverview value)? $default,){ +final _that = this; +switch (_that) { +case _DailyChallengeOverview() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, int challengeNumber, DateTime date, String title)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _DailyChallengeOverview() when $default != null: +return $default(_that.id,_that.challengeNumber,_that.date,_that.title);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, int challengeNumber, DateTime date, String title) $default,) {final _that = this; +switch (_that) { +case _DailyChallengeOverview(): +return $default(_that.id,_that.challengeNumber,_that.date,_that.title);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, int challengeNumber, DateTime date, String title)? $default,) {final _that = this; +switch (_that) { +case _DailyChallengeOverview() when $default != null: +return $default(_that.id,_that.challengeNumber,_that.date,_that.title);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _DailyChallengeOverview extends DailyChallengeOverview { + const _DailyChallengeOverview({required this.id, required this.challengeNumber, required this.date, required this.title}): super._(); + factory _DailyChallengeOverview.fromJson(Map json) => _$DailyChallengeOverviewFromJson(json); + +@override final String id; +@override final int challengeNumber; +@override final DateTime date; +@override final String title; + +/// Create a copy of DailyChallengeOverview +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$DailyChallengeOverviewCopyWith<_DailyChallengeOverview> get copyWith => __$DailyChallengeOverviewCopyWithImpl<_DailyChallengeOverview>(this, _$identity); + +@override +Map toJson() { + return _$DailyChallengeOverviewToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _DailyChallengeOverview&&(identical(other.id, id) || other.id == id)&&(identical(other.challengeNumber, challengeNumber) || other.challengeNumber == challengeNumber)&&(identical(other.date, date) || other.date == date)&&(identical(other.title, title) || other.title == title)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,challengeNumber,date,title); + +@override +String toString() { + return 'DailyChallengeOverview(id: $id, challengeNumber: $challengeNumber, date: $date, title: $title)'; +} + + +} + +/// @nodoc +abstract mixin class _$DailyChallengeOverviewCopyWith<$Res> implements $DailyChallengeOverviewCopyWith<$Res> { + factory _$DailyChallengeOverviewCopyWith(_DailyChallengeOverview value, $Res Function(_DailyChallengeOverview) _then) = __$DailyChallengeOverviewCopyWithImpl; +@override @useResult +$Res call({ + String id, int challengeNumber, DateTime date, String title +}); + + + + +} +/// @nodoc +class __$DailyChallengeOverviewCopyWithImpl<$Res> + implements _$DailyChallengeOverviewCopyWith<$Res> { + __$DailyChallengeOverviewCopyWithImpl(this._self, this._then); + + final _DailyChallengeOverview _self; + final $Res Function(_DailyChallengeOverview) _then; + +/// Create a copy of DailyChallengeOverview +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? challengeNumber = null,Object? date = null,Object? title = null,}) { + return _then(_DailyChallengeOverview( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,challengeNumber: null == challengeNumber ? _self.challengeNumber : challengeNumber // ignore: cast_nullable_to_non_nullable +as int,date: null == date ? _self.date : date // ignore: cast_nullable_to_non_nullable +as DateTime,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/learn/models/daily_challenge_model.g.dart b/mobile-app-new/lib/learn/models/daily_challenge_model.g.dart new file mode 100644 index 000000000..6132c856c --- /dev/null +++ b/mobile-app-new/lib/learn/models/daily_challenge_model.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'daily_challenge_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_DailyChallengeOverview _$DailyChallengeOverviewFromJson( + Map json, +) => _DailyChallengeOverview( + id: json['id'] as String, + challengeNumber: (json['challengeNumber'] as num).toInt(), + date: DateTime.parse(json['date'] as String), + title: json['title'] as String, +); + +Map _$DailyChallengeOverviewToJson( + _DailyChallengeOverview instance, +) => { + 'id': instance.id, + 'challengeNumber': instance.challengeNumber, + 'date': instance.date.toIso8601String(), + 'title': instance.title, +}; diff --git a/mobile-app-new/lib/learn/repositories/curriculum_repository.dart b/mobile-app-new/lib/learn/repositories/curriculum_repository.dart new file mode 100644 index 000000000..da293a706 --- /dev/null +++ b/mobile-app-new/lib/learn/repositories/curriculum_repository.dart @@ -0,0 +1,51 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mobile_app_new/services/dio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'curriculum_repository.g.dart'; + +const _prodUrl = 'https://www.freecodecamp.org/curriculum-data/v2'; +const _devUrl = 'https://www.freecodecamp.dev/curriculum-data/v2'; + +@riverpod +LearnCurriculumRepository learnCurriculumRepository(Ref ref) => + LearnCurriculumRepository(ref.watch(dioProvider)); + +class LearnCurriculumRepository { + LearnCurriculumRepository(this._dio); + + final Dio _dio; + + String get _baseUrl => + dotenv.getBool('DEVELOPMENT_MODE', fallback: false) ? _devUrl : _prodUrl; + + Future>>> getSuperBlockStages() async { + final response = await _dio.get>( + '$_baseUrl/available-superblocks.json', + ); + final stages = response.data?['superblocks']; + + return { + if (stages is Map) + for (final MapEntry(:key, :value) in stages.entries) + if (key is String && value is List) + key: [ + for (final entry in value) + if (entry is Map) Map.from(entry), + ], + }; + } + + Future> getSuperBlock(String dashedName) async { + final response = await _dio.get>( + '$_baseUrl/$dashedName.json', + ); + final superBlock = response.data?.values.firstOrNull; + + if (superBlock is! Map) { + throw FormatException('No superblock in $dashedName.json'); + } + return Map.from(superBlock); + } +} diff --git a/mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart b/mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart new file mode 100644 index 000000000..fe9f4eec6 --- /dev/null +++ b/mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'curriculum_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(learnCurriculumRepository) +final learnCurriculumRepositoryProvider = LearnCurriculumRepositoryProvider._(); + +final class LearnCurriculumRepositoryProvider + extends + $FunctionalProvider< + LearnCurriculumRepository, + LearnCurriculumRepository, + LearnCurriculumRepository + > + with $Provider { + LearnCurriculumRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'learnCurriculumRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$learnCurriculumRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + LearnCurriculumRepository create(Ref ref) { + return learnCurriculumRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(LearnCurriculumRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$learnCurriculumRepositoryHash() => + r'c8bbb55b468c9158e97204e26db19a27b238d89d'; diff --git a/mobile-app-new/lib/learn/repositories/daily_challenge_repository.dart b/mobile-app-new/lib/learn/repositories/daily_challenge_repository.dart new file mode 100644 index 000000000..e2958ee2c --- /dev/null +++ b/mobile-app-new/lib/learn/repositories/daily_challenge_repository.dart @@ -0,0 +1,36 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mobile_app_new/services/dio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'daily_challenge_repository.g.dart'; + +const _prodUrl = 'https://api.freecodecamp.org/daily-coding-challenge'; +const _devUrl = 'https://api.freecodecamp.dev/daily-coding-challenge'; + +@riverpod +LearnDailyChallengeRepository learnDailyChallengeRepository(Ref ref) => + LearnDailyChallengeRepository(ref.watch(dioProvider)); + +class LearnDailyChallengeRepository { + LearnDailyChallengeRepository(this._dio); + + final Dio _dio; + + String get _baseUrl => + dotenv.getBool('DEVELOPMENT_MODE', fallback: false) ? _devUrl : _prodUrl; + + Future> getToday() async { + final response = await _dio.get>('$_baseUrl/today'); + return response.data ?? {}; + } + + Future>> getAll() async { + final response = await _dio.get>('$_baseUrl/all'); + + return [ + for (final entry in response.data ?? const []) + if (entry is Map) Map.from(entry), + ]; + } +} diff --git a/mobile-app-new/lib/learn/repositories/daily_challenge_repository.g.dart b/mobile-app-new/lib/learn/repositories/daily_challenge_repository.g.dart new file mode 100644 index 000000000..a6d01e0ec --- /dev/null +++ b/mobile-app-new/lib/learn/repositories/daily_challenge_repository.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'daily_challenge_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(learnDailyChallengeRepository) +final learnDailyChallengeRepositoryProvider = + LearnDailyChallengeRepositoryProvider._(); + +final class LearnDailyChallengeRepositoryProvider + extends + $FunctionalProvider< + LearnDailyChallengeRepository, + LearnDailyChallengeRepository, + LearnDailyChallengeRepository + > + with $Provider { + LearnDailyChallengeRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'learnDailyChallengeRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$learnDailyChallengeRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + LearnDailyChallengeRepository create(Ref ref) { + return learnDailyChallengeRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(LearnDailyChallengeRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider( + value, + ), + ); + } +} + +String _$learnDailyChallengeRepositoryHash() => + r'f0b3449fec3dd27a62ca11a3751026067c2a57a6'; diff --git a/mobile-app-new/lib/learn/repositories/quote_repository.dart b/mobile-app-new/lib/learn/repositories/quote_repository.dart new file mode 100644 index 000000000..53b268295 --- /dev/null +++ b/mobile-app-new/lib/learn/repositories/quote_repository.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'quote_repository.g.dart'; + +@riverpod +LearnQuoteRepository learnQuoteRepository(Ref ref) => LearnQuoteRepository(); + +class LearnQuoteRepository { + Future>> getQuotes() async { + final file = await rootBundle.loadString( + 'assets/learn/motivational-quotes.json', + ); + final quotes = + (jsonDecode(file) as Map)['motivationalQuotes']; + + return [ + for (final entry in quotes is List ? quotes : const []) + if (entry is Map) entry, + ]; + } +} diff --git a/mobile-app-new/lib/learn/repositories/quote_repository.g.dart b/mobile-app-new/lib/learn/repositories/quote_repository.g.dart new file mode 100644 index 000000000..a81842af0 --- /dev/null +++ b/mobile-app-new/lib/learn/repositories/quote_repository.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'quote_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(learnQuoteRepository) +final learnQuoteRepositoryProvider = LearnQuoteRepositoryProvider._(); + +final class LearnQuoteRepositoryProvider + extends + $FunctionalProvider< + LearnQuoteRepository, + LearnQuoteRepository, + LearnQuoteRepository + > + with $Provider { + LearnQuoteRepositoryProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'learnQuoteRepositoryProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$learnQuoteRepositoryHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + LearnQuoteRepository create(Ref ref) { + return learnQuoteRepository(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(LearnQuoteRepository value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$learnQuoteRepositoryHash() => + r'c041858f71da9f39927dbe81cba59105f8d9431a'; diff --git a/mobile-app-new/lib/learn/services/curriculum_service.dart b/mobile-app-new/lib/learn/services/curriculum_service.dart new file mode 100644 index 000000000..3ce57689b --- /dev/null +++ b/mobile-app-new/lib/learn/services/curriculum_service.dart @@ -0,0 +1,166 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mobile_app_new/learn/constants/curriculum.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/repositories/curriculum_repository.dart'; +import 'package:mobile_app_new/services/remote_config_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'curriculum_service.g.dart'; + +typedef CurriculumStage = ({ + String key, + String title, + List superBlocks, +}); + +@riverpod +LearnCurriculumService learnCurriculumService(Ref ref) => + LearnCurriculumService(ref.watch(learnCurriculumRepositoryProvider)); + +class LearnCurriculumService { + LearnCurriculumService(this._repo); + + final LearnCurriculumRepository _repo; + + Future> getStages(RemoteConfig remoteConfig) async { + final stages = await _repo.getSuperBlockStages(); + final showAll = dotenv.getBool('SHOWALLSB', fallback: false); + + return [ + for (final MapEntry(key: stage, value: title) in stageTitles.entries) + if (stages[stage] case final entries?) + ( + key: stage, + title: title, + superBlocks: [ + for (final entry in entries) + if (!(entry['dashedName'] as String).contains('full-stack')) + _toSummary(entry, remoteConfig, showAll), + ], + ), + ]; + } + + SuperBlockSummary _toSummary( + Map raw, + RemoteConfig remoteConfig, + bool showAll, + ) { + final dashedName = raw['dashedName'] as String; + final override = remoteConfig.activationOverride(dashedName); + + return SuperBlockSummary( + dashedName: dashedName, + title: raw['title'] as String, + isPublic: override ?? (showAll || raw['public'] == true), + isDisabledByOverride: override == false, + ); + } + + Future getSuperBlock( + String dashedName, + RemoteConfig remoteConfig, + ) async { + final raw = await _repo.getSuperBlock(dashedName); + + bool isActive(Map block) => + remoteConfig.activationOverride( + dashedName, + _meta(block)['dashedName'] as String, + ) != + false; + + if (!chapterBasedSuperBlocks.contains(dashedName)) { + final blocks = (raw['blocks'] as List).cast>() + ..sort((a, b) => _order(a).compareTo(_order(b))); + + return SuperBlock.blocks([ + for (final block in blocks) + _toBlock(block, dashedName, isDisabled: !isActive(block)), + ]); + } + + // NOTE: Unlike the flat path, disabled blocks are removed here, along with + // any module or chapter they leave empty. + return SuperBlock.chapters([ + for (final chapter + in (raw['chapters'] as List).cast>()) + if (_toChapter(chapter, dashedName, isActive) case final chapter + when chapter.modules.isNotEmpty) + chapter, + ]); + } + + Chapter _toChapter( + Map raw, + String superBlock, + bool Function(Map) isActive, + ) { + final modules = (raw['modules'] as List).cast>(); + + return Chapter( + dashedName: raw['dashedName'] as String, + name: raw['name'] as String, + comingSoon: raw['comingSoon'] == true, + modules: [ + for (final module in modules) + if (_toModule(module, superBlock, isActive) case final module + when module.blocks.isNotEmpty) + module, + ], + ); + } + + Module _toModule( + Map raw, + String superBlock, + bool Function(Map) isActive, + ) { + final blocks = (raw['blocks'] as List).cast>(); + + return Module( + dashedName: raw['dashedName'] as String, + name: raw['name'] as String? ?? 'No name', + comingSoon: raw['comingSoon'] == true, + moduleType: ModuleType.fromValue(raw['moduleType']), + blocks: [ + for (final block in blocks) + if (isActive(block)) _toBlock(block, superBlock), + ], + ); + } + + Block _toBlock( + Map raw, + String superBlock, { + bool isDisabled = false, + }) { + final meta = _meta(raw); + final description = [ + for (final paragraph in raw['intro'] as List? ?? const []) + if (paragraph is String && paragraph.trim().isNotEmpty) paragraph, + ]; + + return Block( + superBlock: superBlock, + dashedName: meta['dashedName'] as String, + name: meta['name'] as String, + layout: BlockLayout.fromValue(meta['blockLayout']), + // NOTE: `blockType` is the older name for `blockLabel`. + label: BlockLabel.fromValue(meta['blockLabel'] ?? meta['blockType']), + description: description.isEmpty + ? const ['Continue to improve your skills'] + : description, + challenges: [ + for (final challenge in meta['challengeOrder'] as List) + ChallengeOrder.fromJson(challenge as Map), + ], + isDisabledByOverride: isDisabled, + ); + } + + Map _meta(Map block) => + block['meta'] as Map; + + int _order(Map block) => _meta(block)['order'] as int? ?? 0; +} diff --git a/mobile-app-new/lib/learn/services/curriculum_service.g.dart b/mobile-app-new/lib/learn/services/curriculum_service.g.dart new file mode 100644 index 000000000..fab45ac00 --- /dev/null +++ b/mobile-app-new/lib/learn/services/curriculum_service.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'curriculum_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(learnCurriculumService) +final learnCurriculumServiceProvider = LearnCurriculumServiceProvider._(); + +final class LearnCurriculumServiceProvider + extends + $FunctionalProvider< + LearnCurriculumService, + LearnCurriculumService, + LearnCurriculumService + > + with $Provider { + LearnCurriculumServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'learnCurriculumServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$learnCurriculumServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + LearnCurriculumService create(Ref ref) { + return learnCurriculumService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(LearnCurriculumService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$learnCurriculumServiceHash() => + r'4d2cb5cb3589a54d6d867bd3fa48c7fb634e8d12'; diff --git a/mobile-app-new/lib/learn/services/daily_challenge_service.dart b/mobile-app-new/lib/learn/services/daily_challenge_service.dart new file mode 100644 index 000000000..16384430a --- /dev/null +++ b/mobile-app-new/lib/learn/services/daily_challenge_service.dart @@ -0,0 +1,47 @@ +import 'package:collection/collection.dart'; +import 'package:mobile_app_new/learn/models/daily_challenge_model.dart'; +import 'package:mobile_app_new/learn/repositories/daily_challenge_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'daily_challenge_service.g.dart'; + +typedef DailyChallengeMonth = ({ + DateTime month, + List challenges, +}); + +@riverpod +LearnDailyChallengeService learnDailyChallengeService(Ref ref) => + LearnDailyChallengeService( + ref.watch(learnDailyChallengeRepositoryProvider), + ); + +class LearnDailyChallengeService { + LearnDailyChallengeService(this._repo); + + final LearnDailyChallengeRepository _repo; + + Future getToday() async => + DailyChallengeOverview.fromJson(await _repo.getToday()); + + Future> getArchive() async { + final challenges = [ + for (final raw in await _repo.getAll()) + DailyChallengeOverview.fromJson(raw), + ]; + final byMonth = groupBy( + challenges, + (challenge) => DateTime.utc(challenge.date.year, challenge.date.month), + ); + + return [ + for (final month in byMonth.keys.sorted((a, b) => b.compareTo(a))) + ( + month: month, + challenges: byMonth[month]!.sorted( + (a, b) => b.challengeNumber.compareTo(a.challengeNumber), + ), + ), + ]; + } +} diff --git a/mobile-app-new/lib/learn/services/daily_challenge_service.g.dart b/mobile-app-new/lib/learn/services/daily_challenge_service.g.dart new file mode 100644 index 000000000..caa1a0476 --- /dev/null +++ b/mobile-app-new/lib/learn/services/daily_challenge_service.g.dart @@ -0,0 +1,59 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'daily_challenge_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(learnDailyChallengeService) +final learnDailyChallengeServiceProvider = + LearnDailyChallengeServiceProvider._(); + +final class LearnDailyChallengeServiceProvider + extends + $FunctionalProvider< + LearnDailyChallengeService, + LearnDailyChallengeService, + LearnDailyChallengeService + > + with $Provider { + LearnDailyChallengeServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'learnDailyChallengeServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$learnDailyChallengeServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + LearnDailyChallengeService create(Ref ref) { + return learnDailyChallengeService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(LearnDailyChallengeService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$learnDailyChallengeServiceHash() => + r'bf98557e41cd774f97cdd444e63293bf72047e4f'; diff --git a/mobile-app-new/lib/learn/services/quote_service.dart b/mobile-app-new/lib/learn/services/quote_service.dart new file mode 100644 index 000000000..acca5bef0 --- /dev/null +++ b/mobile-app-new/lib/learn/services/quote_service.dart @@ -0,0 +1,25 @@ +import 'dart:math'; + +import 'package:mobile_app_new/learn/repositories/quote_repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'quote_service.g.dart'; + +typedef MotivationalQuote = ({String quote, String author}); + +@riverpod +LearnQuoteService learnQuoteService(Ref ref) => + LearnQuoteService(ref.watch(learnQuoteRepositoryProvider)); + +class LearnQuoteService { + LearnQuoteService(this._repo); + + final LearnQuoteRepository _repo; + + Future getRandomQuote() async { + final quotes = await _repo.getQuotes(); + final quote = quotes[Random().nextInt(quotes.length)]; + + return (quote: quote['quote'] as String, author: quote['author'] as String); + } +} diff --git a/mobile-app-new/lib/learn/services/quote_service.g.dart b/mobile-app-new/lib/learn/services/quote_service.g.dart new file mode 100644 index 000000000..c6a5d4d0f --- /dev/null +++ b/mobile-app-new/lib/learn/services/quote_service.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'quote_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(learnQuoteService) +final learnQuoteServiceProvider = LearnQuoteServiceProvider._(); + +final class LearnQuoteServiceProvider + extends + $FunctionalProvider< + LearnQuoteService, + LearnQuoteService, + LearnQuoteService + > + with $Provider { + LearnQuoteServiceProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'learnQuoteServiceProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$learnQuoteServiceHash(); + + @$internal + @override + $ProviderElement $createElement( + $ProviderPointer pointer, + ) => $ProviderElement(pointer); + + @override + LearnQuoteService create(Ref ref) { + return learnQuoteService(ref); + } + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(LearnQuoteService value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$learnQuoteServiceHash() => r'8dc7e1cb9a76a8d1df48b44215bc416a49dd2c15'; diff --git a/mobile-app-new/lib/learn/ui/challenge/challenge_placeholder_view.dart b/mobile-app-new/lib/learn/ui/challenge/challenge_placeholder_view.dart new file mode 100644 index 000000000..a68f6b158 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/challenge_placeholder_view.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +class ChallengePlaceholderView extends StatelessWidget { + const ChallengePlaceholderView({super.key, required this.details}); + + final String details; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Challenge')), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: Text('Coming soon\n\n$details', textAlign: TextAlign.center), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_view.dart b/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_view.dart new file mode 100644 index 000000000..f8d4e5d92 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_view.dart @@ -0,0 +1,242 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/daily_challenge_model.dart'; +import 'package:mobile_app_new/learn/services/daily_challenge_service.dart'; +import 'package:mobile_app_new/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.dart'; +import 'package:mobile_app_new/routing/learn.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +class DailyChallengeArchiveView extends ConsumerStatefulWidget { + const DailyChallengeArchiveView({super.key}); + + @override + ConsumerState createState() => + _DailyChallengeArchiveViewState(); +} + +class _DailyChallengeArchiveViewState + extends ConsumerState { + final _toggled = {}; + + @override + Widget build(BuildContext context) { + final archive = ref.watch(dailyChallengeArchiveProvider); + final completedChallenges = ref.watch( + authProvider.select((s) => s.value?.completedDailyCodingChallenges), + ); + final completed = {...?completedChallenges?.map((c) => c.id)}; + + return Scaffold( + appBar: AppBar(title: const Text('Daily Coding Challenges')), + drawer: const DrawerWidget(), + body: archive.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => ErrorRetry( + message: 'No challenges available at the moment.', + onRetry: () => ref.invalidate(dailyChallengeArchiveProvider), + ), + data: (months) => months.isEmpty + ? const ErrorRetry( + message: 'No challenges available at the moment.', + ) + : ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: months.length, + itemBuilder: (context, index) { + final month = months[index].month; + + return _MonthCard( + key: ValueKey(month), + month: months[index], + completed: completed, + isOpen: (index == 0) != _toggled.contains(month), + onToggle: () => setState(() { + if (!_toggled.remove(month)) _toggled.add(month); + }), + ); + }, + ), + ), + ); + } +} + +class _MonthCard extends StatelessWidget { + const _MonthCard({ + super.key, + required this.month, + required this.completed, + required this.isOpen, + required this.onToggle, + }); + + final DailyChallengeMonth month; + final Set completed; + final bool isOpen; + final VoidCallback onToggle; + + @override + Widget build(BuildContext context) { + final challenges = month.challenges; + final done = challenges.where((c) => completed.contains(c.id)).length; + final jiffy = Jiffy.parseFromDateTime(month.month); + + return Container( + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: FccColors.gray75), + color: FccColors.gray85, + ), + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.all(8), + child: Icon( + Icons.calendar_today, + color: FccColors.gray00, + size: 30, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + jiffy.format(pattern: 'MMMM'), + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: FccColors.gray00, + ), + ), + ), + ), + ], + ), + if (done == challenges.length) + const Padding( + padding: EdgeInsets.all(8), + child: Row( + children: [ + Padding( + padding: EdgeInsets.only(right: 8), + child: Icon(Icons.check_circle, color: FccColors.blue30), + ), + Text( + 'Completed', + style: TextStyle( + color: FccColors.blue30, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ) + else + Padding( + padding: const EdgeInsets.all(10), + child: LinearProgressIndicator( + minHeight: 12, + value: done == 0 ? 0.01 : done / challenges.length, + valueColor: const AlwaysStoppedAnimation( + FccColors.blue30, + ), + backgroundColor: FccColors.gray80, + borderRadius: BorderRadius.circular(10), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Text( + 'Explore the daily coding challenges for ${jiffy.format(pattern: 'MMMM')}. ' + 'Stay motivated and keep your learning streak alive!', + style: const TextStyle(fontSize: 20, color: FccColors.gray05), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextButton( + onPressed: onToggle, + style: TextButton.styleFrom( + backgroundColor: FccColors.gray85, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: const BorderSide(color: FccColors.gray75), + ), + ), + child: Text(isOpen ? 'Hide Challenges' : 'Show Challenges'), + ), + ), + if (isOpen) + for (final challenge in challenges) + _ChallengeItem( + challenge: challenge, + isCompleted: completed.contains(challenge.id), + ), + ], + ), + ); + } +} + +class _ChallengeItem extends StatelessWidget { + const _ChallengeItem({required this.challenge, required this.isCompleted}); + + final DailyChallengeOverview challenge; + final bool isCompleted; + + @override + Widget build(BuildContext context) { + final title = 'Challenge ${challenge.challengeNumber}: ${challenge.title}'; + + return Semantics( + label: '$title, ${isCompleted ? 'completed' : 'not completed'}', + button: true, + excludeSemantics: true, + child: Card( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: BorderSide( + color: isCompleted ? FccColors.blue10 : FccColors.gray75, + ), + ), + color: isCompleted ? FccColors.blue90Translucent : FccColors.gray80, + child: InkWell( + onTap: () => LearnDailyChallengeRoute( + day: challenge.monthDay, + ).push(context), + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + if (isCompleted) + const Padding( + padding: EdgeInsets.only(right: 8), + child: Icon( + Icons.check_circle, + color: FccColors.blue10, + size: 20, + ), + ), + Expanded( + child: Text(title, style: const TextStyle(fontSize: 18)), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.dart b/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.dart new file mode 100644 index 000000000..0f165df5a --- /dev/null +++ b/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.dart @@ -0,0 +1,8 @@ +import 'package:mobile_app_new/learn/services/daily_challenge_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'daily_challenge_archive_viewmodel.g.dart'; + +@riverpod +Future> dailyChallengeArchive(Ref ref) => + ref.watch(learnDailyChallengeServiceProvider).getArchive(); diff --git a/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.g.dart b/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.g.dart new file mode 100644 index 000000000..f55d487f1 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.g.dart @@ -0,0 +1,52 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'daily_challenge_archive_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(dailyChallengeArchive) +final dailyChallengeArchiveProvider = DailyChallengeArchiveProvider._(); + +final class DailyChallengeArchiveProvider + extends + $FunctionalProvider< + AsyncValue>, + List, + FutureOr> + > + with + $FutureModifier>, + $FutureProvider> { + DailyChallengeArchiveProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'dailyChallengeArchiveProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$dailyChallengeArchiveHash(); + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + return dailyChallengeArchive(ref); + } +} + +String _$dailyChallengeArchiveHash() => + r'5d2c5c749cdd143306987cf2a129e8e3ef7f4693'; diff --git a/mobile-app-new/lib/learn/ui/home_page.dart b/mobile-app-new/lib/learn/ui/home_page.dart deleted file mode 100644 index 5cff6668f..000000000 --- a/mobile-app-new/lib/learn/ui/home_page.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mobile_app_new/widgets/drawer/drawer.dart'; - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - drawer: const DrawerWidget(), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } -} diff --git a/mobile-app-new/lib/learn/ui/landing/daily_challenge_card.dart b/mobile-app-new/lib/learn/ui/landing/daily_challenge_card.dart new file mode 100644 index 000000000..f70736ef0 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/landing/daily_challenge_card.dart @@ -0,0 +1,218 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/ui/landing/landing_viewmodel.dart'; +import 'package:mobile_app_new/routing/learn.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:timezone/timezone.dart' as tz; + +class DailyChallengeCard extends ConsumerWidget { + const DailyChallengeCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final challenge = ref.watch(dailyChallengeTodayProvider).value; + if (challenge == null) return const SizedBox.shrink(); + + final isCompleted = ref.watch( + authProvider.select( + (s) => + s.value?.completedDailyCodingChallenges.any( + (completed) => completed.id == challenge.id, + ) ?? + false, + ), + ); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + child: Semantics( + label: isCompleted + ? 'Daily challenge completed. View past challenges.' + : 'Daily challenge card', + container: true, + child: Container( + padding: const EdgeInsets.all(20), + width: double.infinity, + decoration: BoxDecoration( + color: FccColors.gray10, + borderRadius: const BorderRadius.all(Radius.circular(16)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.10), + blurRadius: 16, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + isCompleted + ? "Today's challenge completed!" + : "Today's challenge", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: FccColors.gray80, + ), + textAlign: TextAlign.center, + ), + if (isCompleted) ...[ + const SizedBox(height: 14), + const _Countdown(), + const SizedBox(height: 14), + _CtaButton( + label: 'View past challenges', + tooltip: 'View past daily challenges', + icon: Icons.history, + onPressed: () => const LearnDailyChallengeArchiveRoute() + .push(context), + ), + ] else ...[ + const SizedBox(height: 4), + Text( + challenge.title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: FccColors.gray90, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 14), + const Text( + 'Do you have the skills to complete this challenge?', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 18, color: FccColors.gray85), + ), + const SizedBox(height: 20), + _CtaButton( + label: 'Start the challenge', + tooltip: 'Start the daily challenge now', + icon: Icons.arrow_forward_ios, + onPressed: () => LearnDailyChallengeRoute( + day: challenge.monthDay, + ).push(context), + ), + ], + ], + ), + ), + ), + ); + } +} + +class _CtaButton extends StatelessWidget { + const _CtaButton({ + required this.label, + required this.tooltip, + required this.icon, + required this.onPressed, + }); + + final String label; + final String tooltip; + final IconData icon; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: Tooltip( + message: tooltip, + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: FccColors.yellow50, + foregroundColor: FccColors.gray90, + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: onPressed, + icon: Icon(icon, size: 20), + label: Text( + label, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ), + ); + } +} + +// NOTE: A new daily challenge unlocks at midnight US Central time. +class _Countdown extends ConsumerStatefulWidget { + const _Countdown(); + + @override + ConsumerState<_Countdown> createState() => _CountdownState(); +} + +class _CountdownState extends ConsumerState<_Countdown> { + late final Timer _timer; + late Duration _timeLeft = _untilMidnight(); + + @override + void initState() { + super.initState(); + _timer = Timer.periodic(const Duration(seconds: 1), (_) { + final next = _untilMidnight(); + final passedMidnight = next > _timeLeft; + if (passedMidnight) ref.invalidate(dailyChallengeTodayProvider); + setState(() => _timeLeft = next); + }); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + + Duration _untilMidnight() { + final chicago = tz.getLocation('America/Chicago'); + final now = tz.TZDateTime.now(chicago); + return tz.TZDateTime( + chicago, + now.year, + now.month, + now.day + 1, + ).difference(now); + } + + @override + Widget build(BuildContext context) { + String twoDigits(int n) => n.toString().padLeft(2, '0'); + final timeLeft = + '${twoDigits(_timeLeft.inHours)}:${twoDigits(_timeLeft.inMinutes % 60)}:${twoDigits(_timeLeft.inSeconds % 60)}'; + + return Semantics( + label: 'Countdown timer. Next challenge in $timeLeft', + liveRegion: true, + excludeSemantics: true, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: Text( + 'Next challenge in: $timeLeft', + style: const TextStyle( + color: FccColors.gray90, + fontWeight: FontWeight.w600, + fontSize: 15, + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/landing/landing_view.dart b/mobile-app-new/lib/learn/ui/landing/landing_view.dart new file mode 100644 index 000000000..38aedd50a --- /dev/null +++ b/mobile-app-new/lib/learn/ui/landing/landing_view.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/constants/curriculum.dart'; +import 'package:mobile_app_new/learn/controllers/curriculum_controller.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/services/curriculum_service.dart'; +import 'package:mobile_app_new/learn/ui/landing/daily_challenge_card.dart'; +import 'package:mobile_app_new/learn/ui/landing/landing_viewmodel.dart'; +import 'package:mobile_app_new/learn/ui/landing/superblock_button.dart'; +import 'package:mobile_app_new/routing/auth.dart'; +import 'package:mobile_app_new/routing/learn.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/widgets/drawer/drawer.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +class LearnLandingView extends ConsumerWidget { + const LearnLandingView({super.key}); + + // NOTE: Errors are swallowed because each provider renders its own error state. + Future _refresh(WidgetRef ref) => Future.wait([ + ref.refresh(curriculumStagesProvider.future), + ref.refresh(dailyChallengeTodayProvider.future), + ]).then((_) {}, onError: (_) {}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final stages = ref.watch(curriculumStagesProvider); + final username = ref.watch(authProvider.select((s) => s.value?.username)); + + return Scaffold( + backgroundColor: FccColors.gray90, + appBar: AppBar(title: const Text('LEARN')), + drawer: const DrawerWidget(), + body: RefreshIndicator( + backgroundColor: FccColors.gray90, + color: Colors.white, + onRefresh: () => _refresh(ref), + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 32), + if (username != null) + Text( + 'Welcome back, ${username.startsWith('fcc') ? 'User' : username}', + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.w700, + fontSize: 20, + ), + ), + const _Quote(), + if (username == null) const _LoginButton(), + const DailyChallengeCard(), + ...switch (stages) { + AsyncData(:final value) => [ + for (final stage in value) ..._stage(context, stage), + ], + AsyncError() => [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 64), + child: ErrorRetry( + message: + 'Oops! Something went wrong. Please try again in a moment.', + onRetry: () => _refresh(ref), + ), + ), + ], + _ => [ + const Padding( + padding: EdgeInsets.all(32), + child: Center(child: CircularProgressIndicator()), + ), + ], + }, + ], + ), + ), + ), + ); + } + + List _stage(BuildContext context, CurriculumStage stage) { + return [ + Text(stage.title, style: const TextStyle(fontSize: 21)), + for (final superBlock in stage.superBlocks) + SuperBlockButton( + key: ValueKey(superBlock.dashedName), + title: superBlock.title, + svgAsset: + superBlockIcons[superBlock.dashedName] ?? defaultSuperBlockIcon, + isEnabled: superBlock.isPublic, + onPressed: () => _openSuperBlock(context, superBlock), + ), + // NOTE: The daily challenge archive sits after the core curriculum. + if (stage.key == 'core') ...[ + const Text( + 'A year of coding challenges', + style: TextStyle(fontSize: 21), + ), + SuperBlockButton( + title: 'View challenge archive', + icon: Icons.calendar_today, + onPressed: () => + const LearnDailyChallengeArchiveRoute().push(context), + ), + ], + ]; + } + + void _openSuperBlock(BuildContext context, SuperBlockSummary superBlock) { + if (superBlock.isPublic) { + LearnSuperBlockRoute( + superBlock: superBlock.dashedName, + ).push(context); + return; + } + + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text( + superBlock.isDisabledByOverride + ? 'Temporarily unavailable, come back soon.' + : 'Not available, use the web version', + ), + duration: const Duration(milliseconds: 2500), + ), + ); + } +} + +class _LoginButton extends StatelessWidget { + const _LoginButton(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: FccColors.yellow50, + minimumSize: const Size.fromHeight(50), + shape: const RoundedRectangleBorder(), + ), + onPressed: () => const LoginRoute().push(context), + child: const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Sign in to save your progress', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.black, fontSize: 20), + ), + ), + ), + ); + } +} + +class _Quote extends ConsumerWidget { + const _Quote(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final quote = ref.watch(motivationalQuoteProvider).value; + if (quote == null) return const SizedBox.shrink(); + + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Text( + '"${quote.quote}"', + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 18), + ), + Padding( + padding: const EdgeInsets.all(8), + child: Text( + '- ${quote.author}', + style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 18), + ), + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/landing/landing_viewmodel.dart b/mobile-app-new/lib/learn/ui/landing/landing_viewmodel.dart new file mode 100644 index 000000000..7db9a2ca5 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/landing/landing_viewmodel.dart @@ -0,0 +1,14 @@ +import 'package:mobile_app_new/learn/models/daily_challenge_model.dart'; +import 'package:mobile_app_new/learn/services/daily_challenge_service.dart'; +import 'package:mobile_app_new/learn/services/quote_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'landing_viewmodel.g.dart'; + +@riverpod +Future dailyChallengeToday(Ref ref) => + ref.watch(learnDailyChallengeServiceProvider).getToday(); + +@riverpod +Future motivationalQuote(Ref ref) => + ref.watch(learnQuoteServiceProvider).getRandomQuote(); diff --git a/mobile-app-new/lib/learn/ui/landing/landing_viewmodel.g.dart b/mobile-app-new/lib/learn/ui/landing/landing_viewmodel.g.dart new file mode 100644 index 000000000..a83cbc041 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/landing/landing_viewmodel.g.dart @@ -0,0 +1,93 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'landing_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(dailyChallengeToday) +final dailyChallengeTodayProvider = DailyChallengeTodayProvider._(); + +final class DailyChallengeTodayProvider + extends + $FunctionalProvider< + AsyncValue, + DailyChallengeOverview, + FutureOr + > + with + $FutureModifier, + $FutureProvider { + DailyChallengeTodayProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'dailyChallengeTodayProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$dailyChallengeTodayHash(); + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + return dailyChallengeToday(ref); + } +} + +String _$dailyChallengeTodayHash() => + r'deb320db8627a5fdc11f65c4c8cd99c72a253765'; + +@ProviderFor(motivationalQuote) +final motivationalQuoteProvider = MotivationalQuoteProvider._(); + +final class MotivationalQuoteProvider + extends + $FunctionalProvider< + AsyncValue, + MotivationalQuote, + FutureOr + > + with + $FutureModifier, + $FutureProvider { + MotivationalQuoteProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'motivationalQuoteProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$motivationalQuoteHash(); + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + return motivationalQuote(ref); + } +} + +String _$motivationalQuoteHash() => r'92f518bd2c1edf843c6598880e5bd945610a7c59'; diff --git a/mobile-app-new/lib/learn/ui/landing/superblock_button.dart b/mobile-app-new/lib/learn/ui/landing/superblock_button.dart new file mode 100644 index 000000000..38655b6e0 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/landing/superblock_button.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; + +class SuperBlockButton extends StatelessWidget { + const SuperBlockButton({ + super.key, + required this.title, + required this.onPressed, + this.icon, + this.svgAsset, + this.isEnabled = true, + }); + + final String title; + final VoidCallback onPressed; + final IconData? icon; + final String? svgAsset; + final bool isEnabled; + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: isEnabled ? 1 : 0.7, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4), + constraints: const BoxConstraints(minHeight: 80), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(5), + backgroundColor: FccColors.gray80, + side: const BorderSide(width: 2, color: FccColors.gray75), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + ), + onPressed: onPressed, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(12), + child: SizedBox( + width: 36, + height: 36, + child: svgAsset != null + ? SvgPicture.asset( + svgAsset!, + fit: BoxFit.contain, + colorFilter: const ColorFilter.mode( + FccColors.gray00, + BlendMode.srcIn, + ), + ) + : Icon(icon, color: FccColors.gray00, size: 36), + ), + ), + Expanded( + child: Text(title, style: const TextStyle(fontSize: 20)), + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: Icon(Icons.arrow_forward_ios), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/module/module_view.dart b/mobile-app-new/lib/learn/ui/module/module_view.dart new file mode 100644 index 000000000..0d0de350a --- /dev/null +++ b/mobile-app-new/lib/learn/ui/module/module_view.dart @@ -0,0 +1,49 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/learn/controllers/curriculum_controller.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/ui/widgets/block_card.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +class ModuleView extends ConsumerWidget { + const ModuleView({super.key, required this.superBlock, required this.module}); + + final String superBlock; + final String module; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final provider = superBlockProvider(superBlock); + final found = ref.watch( + provider.select( + (s) => s.whenData( + (superBlock) => switch (superBlock) { + ChapterSuperBlock(:final chapters) => + chapters + .expand((chapter) => chapter.modules) + .firstWhereOrNull((m) => m.dashedName == module), + BlockSuperBlock() => null, + }, + ), + ), + ); + + return Scaffold( + appBar: AppBar(title: Text(found.value?.name ?? '')), + body: found.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => ErrorRetry( + message: 'Something went wrong!', + onRetry: () => ref.invalidate(provider), + ), + data: (module) => module == null + ? const ErrorRetry(message: 'No blocks available right now.') + : BlockList( + blocks: module.blocks, + padding: const EdgeInsets.all(8), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/superblock/superblock_view.dart b/mobile-app-new/lib/learn/ui/superblock/superblock_view.dart new file mode 100644 index 000000000..8f3fc9d57 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/superblock/superblock_view.dart @@ -0,0 +1,303 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/constants/curriculum.dart'; +import 'package:mobile_app_new/learn/controllers/curriculum_controller.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/ui/widgets/block_card.dart'; +import 'package:mobile_app_new/routing/learn.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +class SuperBlockView extends ConsumerStatefulWidget { + const SuperBlockView({super.key, required this.dashedName}); + + final String dashedName; + + @override + ConsumerState createState() => _SuperBlockViewState(); +} + +class _SuperBlockViewState extends ConsumerState { + @override + void initState() { + super.initState(); + unawaited(ref.read(authProvider.notifier).refresh()); + } + + @override + Widget build(BuildContext context) { + final provider = superBlockProvider(widget.dashedName); + final superBlock = ref.watch(provider); + final title = ref.watch( + curriculumStagesProvider.select( + (s) => s.value + ?.expand((stage) => stage.superBlocks) + .firstWhereOrNull((sb) => sb.dashedName == widget.dashedName) + ?.title, + ), + ); + + return Scaffold( + backgroundColor: FccColors.gray90, + appBar: AppBar(title: Text(title ?? '')), + body: superBlock.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => ErrorRetry( + message: 'Something went wrong!', + onRetry: () => ref.invalidate(provider), + ), + data: (superBlock) => RefreshIndicator( + onRefresh: () => + ref.refresh(provider.future).then((_) {}, onError: (_) {}), + backgroundColor: FccColors.gray90, + color: Colors.white, + child: switch (superBlock) { + BlockSuperBlock(:final blocks) => _Blocks(blocks: blocks), + ChapterSuperBlock(:final chapters) => _Chapters( + superBlock: widget.dashedName, + chapters: chapters + .where((c) => !hiddenExamChapters.contains(c.dashedName)) + .toList(), + ), + }, + ), + ), + ); + } +} + +class _Empty extends StatelessWidget { + const _Empty(); + + @override + Widget build(BuildContext context) { + // NOTE: A ListView so pull-to-refresh still works. + return ListView( + children: const [ + Padding( + padding: EdgeInsets.all(64), + child: Center(child: Text('No blocks available right now.')), + ), + ], + ); + } +} + +class _Blocks extends StatelessWidget { + const _Blocks({required this.blocks}); + + final List blocks; + + @override + Widget build(BuildContext context) { + if (blocks.isEmpty) return const _Empty(); + + return BlockList( + blocks: blocks, + padding: const EdgeInsets.fromLTRB(8, 16, 8, 32), + ); + } +} + +class _Chapters extends StatelessWidget { + const _Chapters({required this.superBlock, required this.chapters}); + + final String superBlock; + final List chapters; + + @override + Widget build(BuildContext context) { + if (chapters.isEmpty) return const _Empty(); + + final isDev = dotenv.getBool('DEVELOPMENT_MODE', fallback: false); + + return ListView( + padding: const EdgeInsets.symmetric(vertical: 8), + children: [ + for (final chapter in chapters) + _ChapterCard( + superBlock: superBlock, + chapter: chapter, + isLocked: devOnlyChapters.contains(chapter.dashedName) && !isDev, + ), + ], + ); + } +} + +class _ChapterCard extends StatelessWidget { + const _ChapterCard({ + required this.superBlock, + required this.chapter, + required this.isLocked, + }); + + final String superBlock; + final Chapter chapter; + final bool isLocked; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(8), + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), + decoration: const BoxDecoration( + color: FccColors.gray85, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: Text( + chapter.name, + style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + ), + ), + if (chapter.comingSoon || isLocked) + Container( + margin: const EdgeInsets.all(8), + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + border: Border.all(color: Colors.white, width: 2), + borderRadius: BorderRadius.circular(5), + ), + child: const Text('Coming Soon'), + ), + if (!isLocked) + for (final module in chapter.modules) + if (!module.comingSoon) + _ModuleButton(superBlock: superBlock, module: module), + ], + ), + ); + } +} + +class _ModuleButton extends ConsumerWidget { + const _ModuleButton({required this.superBlock, required this.module}); + + final String superBlock; + final Module module; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final challenges = [for (final block in module.blocks) ...block.challenges]; + final isSingleStep = challenges.length == 1; + final isReview = module.blocks.first.label == BlockLabel.review; + final isCertProject = module.moduleType == ModuleType.certProject; + final color = isReview + ? FccColors.orange30 + : isCertProject + ? FccColors.green40 + : FccColors.blue30; + final textColor = isSingleStep ? color : Colors.white; + + final completedCount = ref.watch( + authProvider.select((s) { + final completed = {...?s.value?.completedChallenges.map((c) => c.id)}; + return challenges.where((c) => completed.contains(c.id)).length; + }), + ); + + final subtitle = isCertProject + ? 'Certification Project' + : isReview + ? 'Review' + : null; + + return Container( + margin: const EdgeInsets.all(5), + constraints: const BoxConstraints(minHeight: 100), + decoration: BoxDecoration( + color: color.withValues(alpha: isSingleStep ? 0.3 : 0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: color.withValues(alpha: isSingleStep ? 0.5 : 0.4), + width: 2, + ), + ), + child: TextButton( + style: TextButton.styleFrom( + padding: const EdgeInsets.all(12), + alignment: Alignment.centerLeft, + minimumSize: const Size.fromHeight(100), + backgroundColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + overlayColor: Colors.white, + ), + onPressed: () { + final block = module.blocks.first; + if (isSingleStep && block.challenges.isNotEmpty) { + LearnChallengeRoute( + superBlock: superBlock, + block: block.dashedName, + challengeId: block.challenges.first.id, + ).push(context); + } else { + LearnModuleRoute( + superBlock: superBlock, + module: module.dashedName, + ).push(context); + } + }, + child: Row( + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + module.name, + style: TextStyle( + fontSize: 21, + fontWeight: FontWeight.bold, + color: textColor, + ), + ), + if (subtitle != null) + Padding( + padding: const EdgeInsets.only(top: 4), + child: Text( + subtitle, + style: TextStyle( + color: color.withValues(alpha: 0.8), + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ), + if (!isSingleStep && !isReview) + Text( + '$completedCount/${challenges.length} Steps Complete', + style: const TextStyle( + color: FccColors.gray15, + fontSize: 18, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + Icon( + isSingleStep + ? Icons.play_arrow_rounded + : Icons.arrow_forward_ios_outlined, + size: 25, + color: textColor, + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/widgets/block_card.dart b/mobile-app-new/lib/learn/ui/widgets/block_card.dart new file mode 100644 index 000000000..1627f15ca --- /dev/null +++ b/mobile-app-new/lib/learn/ui/widgets/block_card.dart @@ -0,0 +1,504 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/routing/learn.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:mobile_app_new/widgets/drawer/scroll_shadow.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; + +const _cardColor = FccColors.gray85; +const _borderColor = FccColors.gray75; +const _tileColor = FccColors.gray80; +const _completedTileColor = FccColors.blue90Translucent; +const _completedBorderColor = FccColors.blue10; + +(IconData?, Color) _labelStyle(BlockLabel label) => switch (label) { + BlockLabel.lecture => (Icons.menu_book_outlined, FccColors.blue30), + BlockLabel.quiz => (Icons.help_outline, FccColors.orange30), + BlockLabel.lab => (Icons.science_outlined, FccColors.green40), + BlockLabel.workshop => (Icons.build_outlined, FccColors.purple10), + BlockLabel.exam => (Icons.school, FccColors.red15), + BlockLabel.review => (Icons.edit_document, FccColors.yellow10), + BlockLabel.warmup => (Icons.fitbit_outlined, FccColors.blue30), + BlockLabel.learn => (Icons.school_outlined, FccColors.green40), + BlockLabel.practice => (Icons.edit_outlined, FccColors.purple10), + BlockLabel.legacy => (null, FccColors.blue05), +}; + +void _openChallenge(BuildContext context, Block block, String challengeId) { + LearnChallengeRoute( + superBlock: block.superBlock, + block: block.dashedName, + challengeId: challengeId, + ).push(context); +} + +void _showUnavailable(BuildContext context) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + const SnackBar( + content: Text('Temporarily unavailable, come back soon.'), + duration: Duration(milliseconds: 2500), + ), + ); +} + +class BlockList extends StatefulWidget { + const BlockList({super.key, required this.blocks, required this.padding}); + + final List blocks; + final EdgeInsets padding; + + @override + State createState() => _BlockListState(); +} + +class _BlockListState extends State { + final _toggled = {}; + + @override + Widget build(BuildContext context) { + return ListView.builder( + padding: widget.padding, + itemCount: widget.blocks.length, + itemBuilder: (context, index) { + final block = widget.blocks[index]; + + return BlockCard( + key: ValueKey(block.dashedName), + block: block, + isOpen: (index == 0) != _toggled.contains(block.dashedName), + onToggle: () => setState(() { + if (!_toggled.remove(block.dashedName)) { + _toggled.add(block.dashedName); + } + }), + ); + }, + ); + } +} + +class BlockCard extends ConsumerWidget { + const BlockCard({ + super.key, + required this.block, + required this.isOpen, + required this.onToggle, + }); + + final Block block; + final bool isOpen; + final VoidCallback onToggle; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final completedChallenges = ref.watch( + authProvider.select((s) => s.value?.completedChallenges), + ); + final completed = {...?completedChallenges?.map((c) => c.id)}; + + final (icon, color) = _labelStyle(block.label); + final isDisabled = block.isDisabledByOverride; + final isOpen = this.isOpen && !isDisabled; + final onToggle = isDisabled + ? () => _showUnavailable(context) + : this.onToggle; + + return Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Opacity( + opacity: isDisabled ? 0.7 : 1, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: _borderColor), + color: _cardColor, + ), + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (icon != null) + Padding( + padding: const EdgeInsets.all(8), + child: Icon(icon, color: color, size: 30), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(8), + child: Text( + block.name, + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: color, + ), + ), + ), + ), + ], + ), + ...switch (block.layout) { + BlockLayout.challengeGrid => [ + _Progress(block: block, completed: completed, color: color), + _Description(block: block), + _ToggleButton( + isOpen: isOpen, + noun: 'Steps', + onPressed: onToggle, + ), + if (isOpen) _StepGrid(block: block, completed: completed), + ], + BlockLayout.challengeList => [ + _Progress( + block: block, + completed: completed, + color: FccColors.blue30, + ), + _Description(block: block), + _ToggleButton( + isOpen: isOpen, + noun: 'Steps', + onPressed: onToggle, + ), + if (isOpen) + for (final challenge in block.challenges) + _ChallengeCard( + title: challenge.title, + isCompleted: completed.contains(challenge.id), + onTap: () => + _openChallenge(context, block, challenge.id), + ), + ], + BlockLayout.challengeDialogue => [ + _Description(block: block), + _ToggleButton( + isOpen: isOpen, + noun: 'Tasks', + onPressed: onToggle, + ), + if (isOpen) _Dialogues(block: block, completed: completed), + ], + BlockLayout.challengeLink => [ + if (block.challenges.every((c) => completed.contains(c.id))) + _CompletedLabel(color: color), + _Description(block: block), + Padding( + padding: const EdgeInsets.all(8), + child: SizedBox( + width: double.infinity, + child: TextButton( + onPressed: isDisabled || block.challenges.isEmpty + ? () => _showUnavailable(context) + : () => _openChallenge( + context, + block, + block.challenges.first.id, + ), + style: TextButton.styleFrom( + backgroundColor: color, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + child: const Text( + 'Start', + style: TextStyle( + fontWeight: FontWeight.bold, + color: FccColors.gray90, + ), + ), + ), + ), + ), + ], + }, + ], + ), + ), + ), + ); + } +} + +class _CompletedLabel extends StatelessWidget { + const _CompletedLabel({required this.color}); + + final Color color; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: Icon(Icons.check_circle, color: color), + ), + Text( + 'Completed', + style: TextStyle(color: color, fontWeight: FontWeight.bold), + ), + ], + ), + ); + } +} + +class _Progress extends StatelessWidget { + const _Progress({ + required this.block, + required this.completed, + required this.color, + }); + + final Block block; + final Set completed; + final Color color; + + @override + Widget build(BuildContext context) { + final total = block.challenges.length; + final done = block.challenges.where((c) => completed.contains(c.id)).length; + if (done == total) return _CompletedLabel(color: color); + + return Padding( + padding: const EdgeInsets.all(10), + child: LinearProgressIndicator( + minHeight: 12, + value: done == 0 ? 0.01 : done / total, + valueColor: AlwaysStoppedAnimation(color), + backgroundColor: _tileColor, + borderRadius: BorderRadius.circular(10), + ), + ); + } +} + +class _Description extends StatelessWidget { + const _Description({required this.block}); + + final Block block; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: HTMLParser(context: context).parse( + '

${block.description.join(' ')}

', + isSelectable: false, + customStyles: { + '*:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6)': Style( + color: FccColors.gray05, + ), + 'p': Style(margin: Margins.zero), + }, + ), + ); + } +} + +class _ToggleButton extends StatelessWidget { + const _ToggleButton({ + required this.isOpen, + required this.noun, + required this.onPressed, + }); + + final bool isOpen; + final String noun; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: TextButton( + onPressed: onPressed, + style: TextButton.styleFrom( + backgroundColor: _cardColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: const BorderSide(color: _borderColor), + ), + ), + child: Text(isOpen ? 'Hide $noun' : 'Show $noun'), + ), + ); + } +} + +class _ChallengeCard extends StatelessWidget { + const _ChallengeCard({ + required this.title, + required this.isCompleted, + required this.onTap, + this.isHeader = false, + }); + + final String title; + final bool isCompleted; + final VoidCallback onTap; + final bool isHeader; + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: BorderSide( + color: isCompleted ? _completedBorderColor : _borderColor, + ), + ), + color: isCompleted ? _completedTileColor : _tileColor, + child: InkWell( + onTap: onTap, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + child: Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: isHeader ? FontWeight.bold : null, + ), + ), + ), + ), + ); + } +} + +class _TileGrid extends StatelessWidget { + const _TileGrid({ + required this.block, + required this.challenges, + required this.completed, + this.scrollable = false, + }); + + final Block block; + final List challenges; + final Set completed; + final bool scrollable; + + int _stepNumber(ChallengeOrder challenge, int index) { + final match = RegExp(r'\d+').firstMatch(challenge.title); + return match != null ? int.parse(match.group(0)!) : index + 1; + } + + @override + Widget build(BuildContext context) { + return GridView.builder( + shrinkWrap: true, + padding: EdgeInsets.zero, + physics: scrollable + ? const AlwaysScrollableScrollPhysics() + : const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 6, + mainAxisSpacing: 3, + crossAxisSpacing: 3, + ), + itemCount: challenges.length, + itemBuilder: (context, index) { + final challenge = challenges[index]; + final isCompleted = completed.contains(challenge.id); + + return TextButton( + onPressed: () => _openChallenge(context, block, challenge.id), + style: TextButton.styleFrom( + backgroundColor: isCompleted ? _completedTileColor : _tileColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: BorderSide( + color: isCompleted ? _completedBorderColor : _borderColor, + ), + ), + ), + child: Text( + '${_stepNumber(challenge, index)}', + semanticsLabel: + '${challenge.title}${isCompleted ? ', completed' : ''}', + ), + ); + }, + ); + } +} + +class _StepGrid extends StatelessWidget { + const _StepGrid({required this.block, required this.completed}); + + final Block block; + final Set completed; + + @override + Widget build(BuildContext context) { + // NOTE: Past four rows the grid scrolls inside a fixed height. + final scrollable = block.challenges.length > 24; + + return Container( + padding: const EdgeInsets.all(8), + constraints: const BoxConstraints(maxHeight: 300), + child: ScrollShadow( + child: _TileGrid( + block: block, + challenges: block.challenges, + completed: completed, + scrollable: scrollable, + ), + ), + ); + } +} + +class _Dialogues extends StatelessWidget { + const _Dialogues({required this.block, required this.completed}); + + final Block block; + final Set completed; + + @override + Widget build(BuildContext context) { + final sections = <({ChallengeOrder header, List tasks})>[]; + for (final (index, challenge) in block.challenges.indexed) { + if (index == 0 || challenge.title.contains('Dialogue')) { + sections.add((header: challenge, tasks: [])); + } else { + sections.last.tasks.add(challenge); + } + } + + return Column( + children: [ + for (final (:header, :tasks) in sections) ...[ + _ChallengeCard( + title: header.title, + isHeader: true, + isCompleted: completed.contains(header.id), + onTap: () => _openChallenge(context, block, header.id), + ), + Padding( + padding: const EdgeInsets.fromLTRB(8, 4, 8, 12), + child: _TileGrid( + block: block, + challenges: tasks, + completed: completed, + ), + ), + ], + ], + ); + } +} diff --git a/mobile-app-new/lib/main.dart b/mobile-app-new/lib/main.dart index f0435e235..831321b6f 100644 --- a/mobile-app-new/lib/main.dart +++ b/mobile-app-new/lib/main.dart @@ -1,17 +1,22 @@ import 'dart:async'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/firebase_options.dart'; import 'package:mobile_app_new/podcasts/services/library_service.dart'; import 'package:mobile_app_new/routing/router.dart'; import 'package:mobile_app_new/services/audio_service.dart'; import 'package:mobile_app_new/services/auth_service.dart'; import 'package:mobile_app_new/services/notification_service.dart'; +import 'package:timezone/data/latest.dart' as tz; Future main() async { await dotenv.load(); + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + tz.initializeTimeZones(); final audioHandler = await initAudioService(); diff --git a/mobile-app-new/lib/news/ui/widgets/post_feed_list.dart b/mobile-app-new/lib/news/ui/widgets/post_feed_list.dart index 9b7c6a9c5..187c0c40d 100644 --- a/mobile-app-new/lib/news/ui/widgets/post_feed_list.dart +++ b/mobile-app-new/lib/news/ui/widgets/post_feed_list.dart @@ -56,7 +56,8 @@ class _PostFeedListState extends ConsumerState { onRetry: () => ref.invalidate(_provider), ), data: (feed) => RefreshIndicator( - onRefresh: () async => ref.invalidate(_provider), + onRefresh: () => + ref.refresh(_provider.future).then((_) {}, onError: (_) {}), backgroundColor: FccColors.gray90, color: Colors.white, child: CustomScrollView( diff --git a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart index 6b0308138..74e330f92 100644 --- a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart +++ b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_view.dart @@ -61,7 +61,8 @@ class _PodcastEpisodeListViewState onRetry: () => ref.invalidate(_provider), ), data: (state) => RefreshIndicator( - onRefresh: () async => ref.invalidate(_provider), + onRefresh: () => + ref.refresh(_provider.future).then((_) {}, onError: (_) {}), backgroundColor: FccColors.gray90, color: Colors.white, child: CustomScrollView( diff --git a/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart index 030a85244..70ef2c2de 100644 --- a/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart +++ b/mobile-app-new/lib/podcasts/ui/podcast_list/podcast_list_view.dart @@ -22,7 +22,9 @@ class PodcastListView extends ConsumerWidget { onRetry: () => ref.invalidate(podcastListProvider), ), data: (podcasts) => RefreshIndicator( - onRefresh: () async => ref.invalidate(podcastListProvider), + onRefresh: () => ref + .refresh(podcastListProvider.future) + .then((_) {}, onError: (_) {}), backgroundColor: FccColors.gray90, color: Colors.white, child: GridView.count( diff --git a/mobile-app-new/lib/routing/learn.dart b/mobile-app-new/lib/routing/learn.dart index cd7e19862..fdedfd653 100644 --- a/mobile-app-new/lib/routing/learn.dart +++ b/mobile-app-new/lib/routing/learn.dart @@ -1,19 +1,92 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:mobile_app_new/learn/ui/home_page.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_placeholder_view.dart'; +import 'package:mobile_app_new/learn/ui/daily_challenge_archive/daily_challenge_archive_view.dart'; +import 'package:mobile_app_new/learn/ui/landing/landing_view.dart'; +import 'package:mobile_app_new/learn/ui/module/module_view.dart'; +import 'package:mobile_app_new/learn/ui/superblock/superblock_view.dart'; part 'learn.g.dart'; const learnLandingPath = '/learn'; +const learnDailyChallengeArchivePath = '/learn/daily-challenges'; +const learnDailyChallengePath = '/learn/daily-challenges/:day'; +const learnSuperBlockPath = '/learn/:superBlock'; +const learnModulePath = '/learn/:superBlock/:module'; +const learnChallengePath = '/learn/:superBlock/:block/:challengeId'; @TypedGoRoute(path: learnLandingPath) class LearnLandingRoute extends GoRouteData with $LearnLandingRoute { - const LearnLandingRoute({this.title = 'Hello there'}); + const LearnLandingRoute(); - final String title; + @override + Widget build(BuildContext context, GoRouterState state) => + const LearnLandingView(); +} + +@TypedGoRoute( + path: learnDailyChallengeArchivePath, +) +class LearnDailyChallengeArchiveRoute extends GoRouteData + with $LearnDailyChallengeArchiveRoute { + const LearnDailyChallengeArchiveRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => + const DailyChallengeArchiveView(); +} + +// TODO: Iteration 4 replaces the placeholder with the daily challenge editor. +@TypedGoRoute(path: learnDailyChallengePath) +class LearnDailyChallengeRoute extends GoRouteData + with $LearnDailyChallengeRoute { + const LearnDailyChallengeRoute({required this.day}); + + // NOTE: MM-DD + final String day; + + @override + Widget build(BuildContext context, GoRouterState state) => + ChallengePlaceholderView(details: 'Daily challenge $day'); +} + +@TypedGoRoute(path: learnSuperBlockPath) +class LearnSuperBlockRoute extends GoRouteData with $LearnSuperBlockRoute { + const LearnSuperBlockRoute({required this.superBlock}); + + final String superBlock; + + @override + Widget build(BuildContext context, GoRouterState state) => + SuperBlockView(dashedName: superBlock); +} + +@TypedGoRoute(path: learnModulePath) +class LearnModuleRoute extends GoRouteData with $LearnModuleRoute { + const LearnModuleRoute({required this.superBlock, required this.module}); + + final String superBlock; + final String module; + + @override + Widget build(BuildContext context, GoRouterState state) => + ModuleView(superBlock: superBlock, module: module); +} + +// TODO: Iterations 3 and 4 replace the placeholder with the challenge views. +@TypedGoRoute(path: learnChallengePath) +class LearnChallengeRoute extends GoRouteData with $LearnChallengeRoute { + const LearnChallengeRoute({ + required this.superBlock, + required this.block, + required this.challengeId, + }); + + final String superBlock; + final String block; + final String challengeId; @override - Widget build(BuildContext context, GoRouterState state) { - return MyHomePage(title: title); - } + Widget build(BuildContext context, GoRouterState state) => + ChallengePlaceholderView(details: '$superBlock/$block/$challengeId'); } diff --git a/mobile-app-new/lib/routing/learn.g.dart b/mobile-app-new/lib/routing/learn.g.dart index 06d6ba291..bd12bbbc7 100644 --- a/mobile-app-new/lib/routing/learn.g.dart +++ b/mobile-app-new/lib/routing/learn.g.dart @@ -6,7 +6,14 @@ part of 'learn.dart'; // GoRouterGenerator // ************************************************************************** -List get $appRoutes => [$learnLandingRoute]; +List get $appRoutes => [ + $learnLandingRoute, + $learnDailyChallengeArchiveRoute, + $learnDailyChallengeRoute, + $learnSuperBlockRoute, + $learnModuleRoute, + $learnChallengeRoute, +]; RouteBase get $learnLandingRoute => GoRouteData.$route( path: '/learn', @@ -15,16 +22,166 @@ RouteBase get $learnLandingRoute => GoRouteData.$route( ); mixin $LearnLandingRoute on GoRouteData { - static LearnLandingRoute _fromState(GoRouterState state) => LearnLandingRoute( - title: state.uri.queryParameters['title'] ?? 'Hello there', + static LearnLandingRoute _fromState(GoRouterState state) => + const LearnLandingRoute(); + + @override + String get location => GoRouteData.$location('/learn'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $learnDailyChallengeArchiveRoute => GoRouteData.$route( + path: '/learn/daily-challenges', + hasOverriddenOnExit: false, + factory: $LearnDailyChallengeArchiveRoute._fromState, +); + +mixin $LearnDailyChallengeArchiveRoute on GoRouteData { + static LearnDailyChallengeArchiveRoute _fromState(GoRouterState state) => + const LearnDailyChallengeArchiveRoute(); + + @override + String get location => GoRouteData.$location('/learn/daily-challenges'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $learnDailyChallengeRoute => GoRouteData.$route( + path: '/learn/daily-challenges/:day', + hasOverriddenOnExit: false, + factory: $LearnDailyChallengeRoute._fromState, +); + +mixin $LearnDailyChallengeRoute on GoRouteData { + static LearnDailyChallengeRoute _fromState(GoRouterState state) => + LearnDailyChallengeRoute(day: state.pathParameters['day']!); + + LearnDailyChallengeRoute get _self => this as LearnDailyChallengeRoute; + + @override + String get location => GoRouteData.$location( + '/learn/daily-challenges/${Uri.encodeComponent(_self.day)}', ); - LearnLandingRoute get _self => this as LearnLandingRoute; + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $learnSuperBlockRoute => GoRouteData.$route( + path: '/learn/:superBlock', + hasOverriddenOnExit: false, + factory: $LearnSuperBlockRoute._fromState, +); + +mixin $LearnSuperBlockRoute on GoRouteData { + static LearnSuperBlockRoute _fromState(GoRouterState state) => + LearnSuperBlockRoute(superBlock: state.pathParameters['superBlock']!); + + LearnSuperBlockRoute get _self => this as LearnSuperBlockRoute; + + @override + String get location => + GoRouteData.$location('/learn/${Uri.encodeComponent(_self.superBlock)}'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $learnModuleRoute => GoRouteData.$route( + path: '/learn/:superBlock/:module', + hasOverriddenOnExit: false, + factory: $LearnModuleRoute._fromState, +); + +mixin $LearnModuleRoute on GoRouteData { + static LearnModuleRoute _fromState(GoRouterState state) => LearnModuleRoute( + superBlock: state.pathParameters['superBlock']!, + module: state.pathParameters['module']!, + ); + + LearnModuleRoute get _self => this as LearnModuleRoute; + + @override + String get location => GoRouteData.$location( + '/learn/${Uri.encodeComponent(_self.superBlock)}/${Uri.encodeComponent(_self.module)}', + ); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $learnChallengeRoute => GoRouteData.$route( + path: '/learn/:superBlock/:block/:challengeId', + hasOverriddenOnExit: false, + factory: $LearnChallengeRoute._fromState, +); + +mixin $LearnChallengeRoute on GoRouteData { + static LearnChallengeRoute _fromState(GoRouterState state) => + LearnChallengeRoute( + superBlock: state.pathParameters['superBlock']!, + block: state.pathParameters['block']!, + challengeId: state.pathParameters['challengeId']!, + ); + + LearnChallengeRoute get _self => this as LearnChallengeRoute; @override String get location => GoRouteData.$location( - '/learn', - queryParams: {if (_self.title != 'Hello there') 'title': _self.title}, + '/learn/${Uri.encodeComponent(_self.superBlock)}/${Uri.encodeComponent(_self.block)}/${Uri.encodeComponent(_self.challengeId)}', ); @override diff --git a/mobile-app-new/lib/services/remote_config_service.dart b/mobile-app-new/lib/services/remote_config_service.dart new file mode 100644 index 000000000..e6725963d --- /dev/null +++ b/mobile-app-new/lib/services/remote_config_service.dart @@ -0,0 +1,72 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'remote_config_service.freezed.dart'; +part 'remote_config_service.g.dart'; + +const _activationOverridesKey = 'activation_overrides'; + +@freezed +abstract class RemoteConfig with _$RemoteConfig { + const factory RemoteConfig({ + @Default({}) Map activationOverrides, + }) = _RemoteConfig; + + const RemoteConfig._(); + + bool? activationOverride(String superBlock, [String? block]) => + activationOverrides[block == null ? superBlock : '$superBlock/$block']; +} + +Duration? _noRetry(int retryCount, Object error) => null; + +@Riverpod(keepAlive: true, retry: _noRetry) +class RemoteConfigNotifier extends _$RemoteConfigNotifier { + @override + Future build() async { + final config = FirebaseRemoteConfig.instance; + + try { + await config.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: const Duration(minutes: 1), + minimumFetchInterval: const Duration(hours: 1), + ), + ); + await config.setDefaults(const {_activationOverridesKey: '{}'}); + await config.fetchAndActivate(); + } catch (e) { + log('Unable to fetch remote config, using cached or default values: $e'); + } + + final sub = config.onConfigUpdated.listen((_) async { + await config.activate(); + if (ref.mounted) state = AsyncData(_read(config)); + }, onError: (Object e) => log('Remote config updates failed: $e')); + ref.onDispose(sub.cancel); + + return _read(config); + } + + RemoteConfig _read(FirebaseRemoteConfig config) => + RemoteConfig(activationOverrides: _activationOverrides(config)); + + Map _activationOverrides(FirebaseRemoteConfig config) { + try { + final decoded = jsonDecode(config.getString(_activationOverridesKey)); + if (decoded is! Map) return const {}; + + return { + for (final MapEntry(:key, :value) in decoded.entries) + if (value is bool) key: value, + }; + } catch (e) { + log('Invalid $_activationOverridesKey, ignoring overrides: $e'); + return const {}; + } + } +} diff --git a/mobile-app-new/lib/services/remote_config_service.freezed.dart b/mobile-app-new/lib/services/remote_config_service.freezed.dart new file mode 100644 index 000000000..e7b417fd0 --- /dev/null +++ b/mobile-app-new/lib/services/remote_config_service.freezed.dart @@ -0,0 +1,277 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'remote_config_service.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$RemoteConfig { + + Map get activationOverrides; +/// Create a copy of RemoteConfig +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$RemoteConfigCopyWith get copyWith => _$RemoteConfigCopyWithImpl(this as RemoteConfig, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is RemoteConfig&&const DeepCollectionEquality().equals(other.activationOverrides, activationOverrides)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(activationOverrides)); + +@override +String toString() { + return 'RemoteConfig(activationOverrides: $activationOverrides)'; +} + + +} + +/// @nodoc +abstract mixin class $RemoteConfigCopyWith<$Res> { + factory $RemoteConfigCopyWith(RemoteConfig value, $Res Function(RemoteConfig) _then) = _$RemoteConfigCopyWithImpl; +@useResult +$Res call({ + Map activationOverrides +}); + + + + +} +/// @nodoc +class _$RemoteConfigCopyWithImpl<$Res> + implements $RemoteConfigCopyWith<$Res> { + _$RemoteConfigCopyWithImpl(this._self, this._then); + + final RemoteConfig _self; + final $Res Function(RemoteConfig) _then; + +/// Create a copy of RemoteConfig +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? activationOverrides = null,}) { + return _then(_self.copyWith( +activationOverrides: null == activationOverrides ? _self.activationOverrides : activationOverrides // ignore: cast_nullable_to_non_nullable +as Map, + )); +} + +} + + +/// Adds pattern-matching-related methods to [RemoteConfig]. +extension RemoteConfigPatterns on RemoteConfig { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _RemoteConfig value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _RemoteConfig() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _RemoteConfig value) $default,){ +final _that = this; +switch (_that) { +case _RemoteConfig(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _RemoteConfig value)? $default,){ +final _that = this; +switch (_that) { +case _RemoteConfig() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( Map activationOverrides)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _RemoteConfig() when $default != null: +return $default(_that.activationOverrides);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( Map activationOverrides) $default,) {final _that = this; +switch (_that) { +case _RemoteConfig(): +return $default(_that.activationOverrides);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( Map activationOverrides)? $default,) {final _that = this; +switch (_that) { +case _RemoteConfig() when $default != null: +return $default(_that.activationOverrides);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _RemoteConfig extends RemoteConfig { + const _RemoteConfig({final Map activationOverrides = const {}}): _activationOverrides = activationOverrides,super._(); + + + final Map _activationOverrides; +@override@JsonKey() Map get activationOverrides { + if (_activationOverrides is EqualUnmodifiableMapView) return _activationOverrides; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_activationOverrides); +} + + +/// Create a copy of RemoteConfig +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$RemoteConfigCopyWith<_RemoteConfig> get copyWith => __$RemoteConfigCopyWithImpl<_RemoteConfig>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _RemoteConfig&&const DeepCollectionEquality().equals(other._activationOverrides, _activationOverrides)); +} + + +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_activationOverrides)); + +@override +String toString() { + return 'RemoteConfig(activationOverrides: $activationOverrides)'; +} + + +} + +/// @nodoc +abstract mixin class _$RemoteConfigCopyWith<$Res> implements $RemoteConfigCopyWith<$Res> { + factory _$RemoteConfigCopyWith(_RemoteConfig value, $Res Function(_RemoteConfig) _then) = __$RemoteConfigCopyWithImpl; +@override @useResult +$Res call({ + Map activationOverrides +}); + + + + +} +/// @nodoc +class __$RemoteConfigCopyWithImpl<$Res> + implements _$RemoteConfigCopyWith<$Res> { + __$RemoteConfigCopyWithImpl(this._self, this._then); + + final _RemoteConfig _self; + final $Res Function(_RemoteConfig) _then; + +/// Create a copy of RemoteConfig +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? activationOverrides = null,}) { + return _then(_RemoteConfig( +activationOverrides: null == activationOverrides ? _self._activationOverrides : activationOverrides // ignore: cast_nullable_to_non_nullable +as Map, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/services/remote_config_service.g.dart b/mobile-app-new/lib/services/remote_config_service.g.dart new file mode 100644 index 000000000..ede3b0f2c --- /dev/null +++ b/mobile-app-new/lib/services/remote_config_service.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'remote_config_service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(RemoteConfigNotifier) +final remoteConfigProvider = RemoteConfigNotifierProvider._(); + +final class RemoteConfigNotifierProvider + extends $AsyncNotifierProvider { + RemoteConfigNotifierProvider._() + : super( + from: null, + argument: null, + retry: _noRetry, + name: r'remoteConfigProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$remoteConfigNotifierHash(); + + @$internal + @override + RemoteConfigNotifier create() => RemoteConfigNotifier(); +} + +String _$remoteConfigNotifierHash() => + r'232d92bceb2d38ef205a020a8a4894117a7f468b'; + +abstract class _$RemoteConfigNotifier extends $AsyncNotifier { + FutureOr build(); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref, RemoteConfig>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, RemoteConfig>, + AsyncValue, + Object?, + Object? + >; + return element.handleCreate(ref, build); + } +} diff --git a/mobile-app-new/lib/widgets/drawer/drawer.dart b/mobile-app-new/lib/widgets/drawer/drawer.dart index 16f91e528..ae8df792d 100644 --- a/mobile-app-new/lib/widgets/drawer/drawer.dart +++ b/mobile-app-new/lib/widgets/drawer/drawer.dart @@ -80,6 +80,12 @@ class DrawerWidget extends ConsumerWidget { }, ), sectionDivider, + DrawerTile( + component: 'Daily Challenges', + icon: Icons.extension, + route: () => + const LearnDailyChallengeArchiveRoute().go(context), + ), DrawerTile( component: 'Learn', route: () => const LearnLandingRoute().go(context), diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 85dcf69f8..a8eef6bbe 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "99.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: f6966125633a34f82d9be2ee895b755cff3554087b1d00a9eb884e5947f4bca9 + url: "https://pub.dev" + source: hosted + version: "1.3.77" algolia_client_core: dependency: transitive description: @@ -409,6 +417,54 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "5fbbaa2aeb08a52feee559f92a62f5a6b04c9bdff81c8f608636771cf0d7b680" + url: "https://pub.dev" + source: hosted + version: "4.15.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "9bfbc85faca09346471ac56fbcee00757ebcfd85887c6f602764eff0001902d8" + url: "https://pub.dev" + source: hosted + version: "8.1.1" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "8ed84f5001dbd7a0390e2818d46bc858ceebb970edededdfc8d6552c76fc2f6e" + url: "https://pub.dev" + source: hosted + version: "3.12.0" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: fa518445638fa3903e359ac544bd3243e72ada025728c8dbfa4b28ae939e8b38 + url: "https://pub.dev" + source: hosted + version: "6.7.0" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: dc5a9713031f2ca744cb266d96a58609e929db19f3d91a1dd6bb8f8a0d77acad + url: "https://pub.dev" + source: hosted + version: "3.0.7" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: "500abf695c370c76a486ad7480a6df7caf55d2d35ccc5c66b9fc9c1c47f2bc14" + url: "https://pub.dev" + source: hosted + version: "1.10.14" fixnum: dependency: transitive description: @@ -627,6 +683,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.2" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" + url: "https://pub.dev" + source: hosted + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -1071,6 +1135,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" path_provider: dependency: "direct main" description: @@ -1549,7 +1621,7 @@ packages: source: hosted version: "0.6.17" timezone: - dependency: transitive + dependency: "direct main" description: name: timezone sha256: "981d1020d6ef8fe1e7b3de5054e5b25579ae7c403d7734adc508ffc47668e9cb" @@ -1636,6 +1708,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.6.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935" + url: "https://pub.dev" + source: hosted + version: "1.2.3" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682" + url: "https://pub.dev" + source: hosted + version: "1.3.0" vector_math: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 6a8e0b453..975235a85 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -66,6 +66,10 @@ dependencies: flutter_secure_storage: 11.2.0 flutter_custom_tabs: 2.6.0 flutter_heatmap_calendar: 1.0.5 + firebase_core: 4.15.0 + firebase_remote_config: 6.7.0 + flutter_svg: 2.3.0 + timezone: 0.11.1 dev_dependencies: flutter_driver: @@ -103,6 +107,7 @@ flutter: assets: - .env - assets/images/ + - assets/learn/ # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg From 7a975024e54c6ea481962b1c04f97eb326cf2582 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Thu, 15 Oct 2026 08:07:02 +0530 Subject: [PATCH 38/52] Learn 3/4 --- .../auth/repositories/session_repository.dart | 14 +- .../lib/learn/constants/curriculum.dart | 8 + .../lib/learn/models/challenge_model.dart | 240 + .../learn/models/challenge_model.freezed.dart | 5219 +++++++++++++++++ .../lib/learn/models/challenge_model.g.dart | 308 + .../lib/learn/models/scene_assets_model.dart | 31 + .../models/scene_assets_model.freezed.dart | 567 ++ .../learn/models/scene_assets_model.g.dart | 42 + .../repositories/curriculum_repository.dart | 59 +- .../repositories/curriculum_repository.g.dart | 2 +- .../learn/services/curriculum_service.dart | 27 +- .../learn/ui/challenge/challenge_view.dart | 106 + .../ui/challenge/challenge_viewmodel.dart | 81 + .../ui/challenge/challenge_viewmodel.g.dart | 196 + .../lib/learn/ui/challenge/english_view.dart | 186 + .../lib/learn/ui/challenge/lesson_view.dart | 139 + .../ui/challenge/python_project_view.dart | 132 + .../lib/learn/ui/challenge/quiz_view.dart | 191 + .../ui/challenge/widgets/assignment_tile.dart | 39 + .../challenge/widgets/challenge_button.dart | 37 + .../ui/challenge/widgets/challenge_card.dart | 34 + .../ui/challenge/widgets/challenge_video.dart | 39 + .../ui/challenge/widgets/complete_button.dart | 71 + .../ui/challenge/widgets/example_editor.dart | 78 + .../ui/challenge/widgets/help_dialog.dart | 285 + .../learn/ui/challenge/widgets/html_card.dart | 31 + .../ui/challenge/widgets/parsed_html.dart | 35 + .../learn/ui/challenge/widgets/questions.dart | 176 + .../challenge/widgets/quiz_audio_player.dart | 164 + .../challenge/widgets/scene/scene_canvas.dart | 152 + .../widgets/scene/scene_controls.dart | 118 + .../challenge/widgets/scene/scene_view.dart | 71 + .../widgets/scene/scene_viewmodel.dart | 337 ++ .../scene/scene_viewmodel.freezed.dart | 576 ++ .../widgets/scene/scene_viewmodel.g.dart | 147 + .../scene_fullscreen_view.dart | 97 + mobile-app-new/lib/routing/learn.dart | 30 +- mobile-app-new/lib/routing/learn.g.dart | 32 + .../lib/services/audio_service.dart | 55 +- 39 files changed, 10116 insertions(+), 36 deletions(-) create mode 100644 mobile-app-new/lib/learn/models/challenge_model.dart create mode 100644 mobile-app-new/lib/learn/models/challenge_model.freezed.dart create mode 100644 mobile-app-new/lib/learn/models/challenge_model.g.dart create mode 100644 mobile-app-new/lib/learn/models/scene_assets_model.dart create mode 100644 mobile-app-new/lib/learn/models/scene_assets_model.freezed.dart create mode 100644 mobile-app-new/lib/learn/models/scene_assets_model.g.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/challenge_view.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.g.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/english_view.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/lesson_view.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/python_project_view.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/quiz_view.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/assignment_tile.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/challenge_button.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/challenge_card.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/challenge_video.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/complete_button.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/example_editor.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/help_dialog.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/html_card.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/parsed_html.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/questions.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_canvas.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_controls.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_view.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.freezed.dart create mode 100644 mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.g.dart create mode 100644 mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart diff --git a/mobile-app-new/lib/auth/repositories/session_repository.dart b/mobile-app-new/lib/auth/repositories/session_repository.dart index 93fdaf034..2303d6665 100644 --- a/mobile-app-new/lib/auth/repositories/session_repository.dart +++ b/mobile-app-new/lib/auth/repositories/session_repository.dart @@ -45,7 +45,7 @@ class AuthSessionRepository { Stream get sessionExpired => _sessionExpired.stream; - String get _apiUrl => dotenv.getBool('DEVELOPMENT_MODE', fallback: false) + String get apiUrl => dotenv.getBool('DEVELOPMENT_MODE', fallback: false) ? 'https://api.freecodecamp.dev' : 'https://api.freecodecamp.org'; @@ -118,15 +118,15 @@ class AuthSessionRepository { Future> mobileLogin(String accessToken) { return _dio.get( - '$_apiUrl/mobile-login', + '$apiUrl/mobile-login', options: Options(headers: {'Authorization': 'Bearer $accessToken'}), ); } Future?> fetchSessionUser() async { - final res = await _authenticated( + final res = await authenticated( (options) => _dio.get>( - '$_apiUrl/user/session-user', + '$apiUrl/user/session-user', options: options, ), ); @@ -140,13 +140,13 @@ class AuthSessionRepository { } Future deleteAccount() async { - await _authenticated( - (options) => _dio.post('$_apiUrl/account/delete', options: options), + await authenticated( + (options) => _dio.post('$apiUrl/account/delete', options: options), ); } // NOTE: Every call carrying the session goes through here, so any 401 signs the user out. - Future _authenticated(Future Function(Options) request) async { + Future authenticated(Future Function(Options) request) async { final tokens = await readTokens(); if (tokens == null) throw SessionExpiredException(); diff --git a/mobile-app-new/lib/learn/constants/curriculum.dart b/mobile-app-new/lib/learn/constants/curriculum.dart index 88299112f..e5ffeaa74 100644 --- a/mobile-app-new/lib/learn/constants/curriculum.dart +++ b/mobile-app-new/lib/learn/constants/curriculum.dart @@ -1,3 +1,11 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +const _prodUrl = 'https://www.freecodecamp.org/curriculum-data/v2'; +const _devUrl = 'https://www.freecodecamp.dev/curriculum-data/v2'; + +String get curriculumDataUrl => + dotenv.getBool('DEVELOPMENT_MODE', fallback: false) ? _devUrl : _prodUrl; + const chapterBasedSuperBlocks = [ 'responsive-web-design-v9', 'javascript-v9', diff --git a/mobile-app-new/lib/learn/models/challenge_model.dart b/mobile-app-new/lib/learn/models/challenge_model.dart new file mode 100644 index 000000000..4d483e5a3 --- /dev/null +++ b/mobile-app-new/lib/learn/models/challenge_model.dart @@ -0,0 +1,240 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'challenge_model.freezed.dart'; +part 'challenge_model.g.dart'; + +@freezed +abstract class Challenge with _$Challenge { + const factory Challenge({ + required String id, + required String block, + required String superBlock, + required String title, + required String dashedName, + required int challengeType, + @Default('') String helpCategory, + @Default('') String description, + @Default('') String instructions, + @Default('') String explanation, + @Default('') String transcript, + String? videoId, + @Default([]) List questions, + @Default([]) List assignments, + @Default([]) List quizzes, + @Default([]) List nodules, + FillInTheBlank? fillInTheBlank, + Scene? scene, + }) = _Challenge; + + factory Challenge.fromJson(Map json) => + _$ChallengeFromJson(json); +} + +@freezed +abstract class Question with _$Question { + const factory Question({ + required String text, + required List answers, + // NOTE: 1-based index into answers. + required int solution, + }) = _Question; + + factory Question.fromJson(Map json) => + _$QuestionFromJson(json); +} + +@freezed +abstract class Answer with _$Answer { + const factory Answer({required String answer, String? feedback}) = _Answer; + + factory Answer.fromJson(Map json) => _$AnswerFromJson(json); +} + +@freezed +abstract class Quiz with _$Quiz { + const factory Quiz({required List questions}) = _Quiz; + + factory Quiz.fromJson(Map json) => _$QuizFromJson(json); +} + +@freezed +abstract class QuizQuestion with _$QuizQuestion { + const factory QuizQuestion({ + required String text, + required String answer, + required List distractors, + QuizAudioData? audioData, + }) = _QuizQuestion; + + factory QuizQuestion.fromJson(Map json) => + _$QuizQuestionFromJson(json); +} + +@freezed +abstract class QuizAudioData with _$QuizAudioData { + const factory QuizAudioData({ + required AudioClip audio, + @Default([]) List transcript, + }) = _QuizAudioData; + + factory QuizAudioData.fromJson(Map json) => + _$QuizAudioDataFromJson(json); +} + +@freezed +abstract class TranscriptLine with _$TranscriptLine { + const factory TranscriptLine({ + required String character, + required String text, + }) = _TranscriptLine; + + factory TranscriptLine.fromJson(Map json) => + _$TranscriptLineFromJson(json); +} + +// NOTE: Timestamps are seconds into the file; a scene's startTime is its delay before the audio starts. +@freezed +abstract class AudioClip with _$AudioClip { + const factory AudioClip({ + required String filename, + @Default(0) num startTime, + num? startTimestamp, + num? finishTimestamp, + }) = _AudioClip; + + const AudioClip._(); + + Uri get url => Uri.parse( + 'https://cdn.freecodecamp.org/curriculum/english/animation-assets/sounds/$filename', + ); + + Duration get start => _seconds(startTimestamp) ?? Duration.zero; + + Duration? get end => _seconds(finishTimestamp); + + String get id => '$filename-$startTimestamp-$finishTimestamp'; + + static Duration? _seconds(num? value) => + value == null ? null : Duration(milliseconds: (value * 1000).round()); + + factory AudioClip.fromJson(Map json) => + _$AudioClipFromJson(json); +} + +@freezed +abstract class FillInTheBlank with _$FillInTheBlank { + const factory FillInTheBlank({ + required String sentence, + required List blanks, + }) = _FillInTheBlank; + + factory FillInTheBlank.fromJson(Map json) => + _$FillInTheBlankFromJson(json); +} + +@freezed +abstract class Blank with _$Blank { + const factory Blank({required String answer, @Default('') String feedback}) = + _Blank; + + factory Blank.fromJson(Map json) => _$BlankFromJson(json); +} + +Object? _readNoduleContents(Map json, String key) => + json[key] ?? json['data']; + +@Freezed(unionKey: 'type') +sealed class Nodule with _$Nodule { + const factory Nodule.paragraph({ + @JsonKey(readValue: _readNoduleContents) required String contents, + }) = ParagraphNodule; + + const factory Nodule.interactiveEditor({ + @JsonKey(readValue: _readNoduleContents) required List files, + }) = InteractiveEditorNodule; + + factory Nodule.fromJson(Map json) => _$NoduleFromJson(json); +} + +@freezed +abstract class NoduleFile with _$NoduleFile { + const factory NoduleFile({ + required String name, + required String ext, + required String contents, + }) = _NoduleFile; + + factory NoduleFile.fromJson(Map json) => + _$NoduleFileFromJson(json); +} + +@freezed +abstract class Scene with _$Scene { + const factory Scene({ + required SceneSetup setup, + required List commands, + }) = _Scene; + + factory Scene.fromJson(Map json) => _$SceneFromJson(json); +} + +@freezed +abstract class SceneSetup with _$SceneSetup { + const factory SceneSetup({ + required String background, + required AudioClip audio, + required List characters, + }) = _SceneSetup; + + factory SceneSetup.fromJson(Map json) => + _$SceneSetupFromJson(json); +} + +@freezed +abstract class SceneCharacter with _$SceneCharacter { + const factory SceneCharacter({ + required String character, + required ScenePosition position, + num? opacity, + }) = _SceneCharacter; + + factory SceneCharacter.fromJson(Map json) => + _$SceneCharacterFromJson(json); +} + +@freezed +abstract class ScenePosition with _$ScenePosition { + const factory ScenePosition({ + required num x, + required num y, + required num z, + }) = _ScenePosition; + + factory ScenePosition.fromJson(Map json) => + _$ScenePositionFromJson(json); +} + +@freezed +abstract class SceneCommand with _$SceneCommand { + const factory SceneCommand({ + required String character, + required num startTime, + num? finishTime, + String? background, + ScenePosition? position, + num? opacity, + SceneDialogue? dialogue, + }) = _SceneCommand; + + factory SceneCommand.fromJson(Map json) => + _$SceneCommandFromJson(json); +} + +@freezed +abstract class SceneDialogue with _$SceneDialogue { + const factory SceneDialogue({required String text, required String align}) = + _SceneDialogue; + + factory SceneDialogue.fromJson(Map json) => + _$SceneDialogueFromJson(json); +} diff --git a/mobile-app-new/lib/learn/models/challenge_model.freezed.dart b/mobile-app-new/lib/learn/models/challenge_model.freezed.dart new file mode 100644 index 000000000..a8fca7889 --- /dev/null +++ b/mobile-app-new/lib/learn/models/challenge_model.freezed.dart @@ -0,0 +1,5219 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'challenge_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$Challenge { + + String get id; String get block; String get superBlock; String get title; String get dashedName; int get challengeType; String get helpCategory; String get description; String get instructions; String get explanation; String get transcript; String? get videoId; List get questions; List get assignments; List get quizzes; List get nodules; FillInTheBlank? get fillInTheBlank; Scene? get scene; +/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ChallengeCopyWith get copyWith => _$ChallengeCopyWithImpl(this as Challenge, _$identity); + + /// Serializes this Challenge to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Challenge&&(identical(other.id, id) || other.id == id)&&(identical(other.block, block) || other.block == block)&&(identical(other.superBlock, superBlock) || other.superBlock == superBlock)&&(identical(other.title, title) || other.title == title)&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.challengeType, challengeType) || other.challengeType == challengeType)&&(identical(other.helpCategory, helpCategory) || other.helpCategory == helpCategory)&&(identical(other.description, description) || other.description == description)&&(identical(other.instructions, instructions) || other.instructions == instructions)&&(identical(other.explanation, explanation) || other.explanation == explanation)&&(identical(other.transcript, transcript) || other.transcript == transcript)&&(identical(other.videoId, videoId) || other.videoId == videoId)&&const DeepCollectionEquality().equals(other.questions, questions)&&const DeepCollectionEquality().equals(other.assignments, assignments)&&const DeepCollectionEquality().equals(other.quizzes, quizzes)&&const DeepCollectionEquality().equals(other.nodules, nodules)&&(identical(other.fillInTheBlank, fillInTheBlank) || other.fillInTheBlank == fillInTheBlank)&&(identical(other.scene, scene) || other.scene == scene)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,block,superBlock,title,dashedName,challengeType,helpCategory,description,instructions,explanation,transcript,videoId,const DeepCollectionEquality().hash(questions),const DeepCollectionEquality().hash(assignments),const DeepCollectionEquality().hash(quizzes),const DeepCollectionEquality().hash(nodules),fillInTheBlank,scene); + +@override +String toString() { + return 'Challenge(id: $id, block: $block, superBlock: $superBlock, title: $title, dashedName: $dashedName, challengeType: $challengeType, helpCategory: $helpCategory, description: $description, instructions: $instructions, explanation: $explanation, transcript: $transcript, videoId: $videoId, questions: $questions, assignments: $assignments, quizzes: $quizzes, nodules: $nodules, fillInTheBlank: $fillInTheBlank, scene: $scene)'; +} + + +} + +/// @nodoc +abstract mixin class $ChallengeCopyWith<$Res> { + factory $ChallengeCopyWith(Challenge value, $Res Function(Challenge) _then) = _$ChallengeCopyWithImpl; +@useResult +$Res call({ + String id, String block, String superBlock, String title, String dashedName, int challengeType, String helpCategory, String description, String instructions, String explanation, String transcript, String? videoId, List questions, List assignments, List quizzes, List nodules, FillInTheBlank? fillInTheBlank, Scene? scene +}); + + +$FillInTheBlankCopyWith<$Res>? get fillInTheBlank;$SceneCopyWith<$Res>? get scene; + +} +/// @nodoc +class _$ChallengeCopyWithImpl<$Res> + implements $ChallengeCopyWith<$Res> { + _$ChallengeCopyWithImpl(this._self, this._then); + + final Challenge _self; + final $Res Function(Challenge) _then; + +/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? block = null,Object? superBlock = null,Object? title = null,Object? dashedName = null,Object? challengeType = null,Object? helpCategory = null,Object? description = null,Object? instructions = null,Object? explanation = null,Object? transcript = null,Object? videoId = freezed,Object? questions = null,Object? assignments = null,Object? quizzes = null,Object? nodules = null,Object? fillInTheBlank = freezed,Object? scene = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,block: null == block ? _self.block : block // ignore: cast_nullable_to_non_nullable +as String,superBlock: null == superBlock ? _self.superBlock : superBlock // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,challengeType: null == challengeType ? _self.challengeType : challengeType // ignore: cast_nullable_to_non_nullable +as int,helpCategory: null == helpCategory ? _self.helpCategory : helpCategory // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,instructions: null == instructions ? _self.instructions : instructions // ignore: cast_nullable_to_non_nullable +as String,explanation: null == explanation ? _self.explanation : explanation // ignore: cast_nullable_to_non_nullable +as String,transcript: null == transcript ? _self.transcript : transcript // ignore: cast_nullable_to_non_nullable +as String,videoId: freezed == videoId ? _self.videoId : videoId // ignore: cast_nullable_to_non_nullable +as String?,questions: null == questions ? _self.questions : questions // ignore: cast_nullable_to_non_nullable +as List,assignments: null == assignments ? _self.assignments : assignments // ignore: cast_nullable_to_non_nullable +as List,quizzes: null == quizzes ? _self.quizzes : quizzes // ignore: cast_nullable_to_non_nullable +as List,nodules: null == nodules ? _self.nodules : nodules // ignore: cast_nullable_to_non_nullable +as List,fillInTheBlank: freezed == fillInTheBlank ? _self.fillInTheBlank : fillInTheBlank // ignore: cast_nullable_to_non_nullable +as FillInTheBlank?,scene: freezed == scene ? _self.scene : scene // ignore: cast_nullable_to_non_nullable +as Scene?, + )); +} +/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$FillInTheBlankCopyWith<$Res>? get fillInTheBlank { + if (_self.fillInTheBlank == null) { + return null; + } + + return $FillInTheBlankCopyWith<$Res>(_self.fillInTheBlank!, (value) { + return _then(_self.copyWith(fillInTheBlank: value)); + }); +}/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SceneCopyWith<$Res>? get scene { + if (_self.scene == null) { + return null; + } + + return $SceneCopyWith<$Res>(_self.scene!, (value) { + return _then(_self.copyWith(scene: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [Challenge]. +extension ChallengePatterns on Challenge { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Challenge value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Challenge() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Challenge value) $default,){ +final _that = this; +switch (_that) { +case _Challenge(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Challenge value)? $default,){ +final _that = this; +switch (_that) { +case _Challenge() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String block, String superBlock, String title, String dashedName, int challengeType, String helpCategory, String description, String instructions, String explanation, String transcript, String? videoId, List questions, List assignments, List quizzes, List nodules, FillInTheBlank? fillInTheBlank, Scene? scene)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Challenge() when $default != null: +return $default(_that.id,_that.block,_that.superBlock,_that.title,_that.dashedName,_that.challengeType,_that.helpCategory,_that.description,_that.instructions,_that.explanation,_that.transcript,_that.videoId,_that.questions,_that.assignments,_that.quizzes,_that.nodules,_that.fillInTheBlank,_that.scene);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String block, String superBlock, String title, String dashedName, int challengeType, String helpCategory, String description, String instructions, String explanation, String transcript, String? videoId, List questions, List assignments, List quizzes, List nodules, FillInTheBlank? fillInTheBlank, Scene? scene) $default,) {final _that = this; +switch (_that) { +case _Challenge(): +return $default(_that.id,_that.block,_that.superBlock,_that.title,_that.dashedName,_that.challengeType,_that.helpCategory,_that.description,_that.instructions,_that.explanation,_that.transcript,_that.videoId,_that.questions,_that.assignments,_that.quizzes,_that.nodules,_that.fillInTheBlank,_that.scene);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String block, String superBlock, String title, String dashedName, int challengeType, String helpCategory, String description, String instructions, String explanation, String transcript, String? videoId, List questions, List assignments, List quizzes, List nodules, FillInTheBlank? fillInTheBlank, Scene? scene)? $default,) {final _that = this; +switch (_that) { +case _Challenge() when $default != null: +return $default(_that.id,_that.block,_that.superBlock,_that.title,_that.dashedName,_that.challengeType,_that.helpCategory,_that.description,_that.instructions,_that.explanation,_that.transcript,_that.videoId,_that.questions,_that.assignments,_that.quizzes,_that.nodules,_that.fillInTheBlank,_that.scene);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Challenge implements Challenge { + const _Challenge({required this.id, required this.block, required this.superBlock, required this.title, required this.dashedName, required this.challengeType, this.helpCategory = '', this.description = '', this.instructions = '', this.explanation = '', this.transcript = '', this.videoId, final List questions = const [], final List assignments = const [], final List quizzes = const [], final List nodules = const [], this.fillInTheBlank, this.scene}): _questions = questions,_assignments = assignments,_quizzes = quizzes,_nodules = nodules; + factory _Challenge.fromJson(Map json) => _$ChallengeFromJson(json); + +@override final String id; +@override final String block; +@override final String superBlock; +@override final String title; +@override final String dashedName; +@override final int challengeType; +@override@JsonKey() final String helpCategory; +@override@JsonKey() final String description; +@override@JsonKey() final String instructions; +@override@JsonKey() final String explanation; +@override@JsonKey() final String transcript; +@override final String? videoId; + final List _questions; +@override@JsonKey() List get questions { + if (_questions is EqualUnmodifiableListView) return _questions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_questions); +} + + final List _assignments; +@override@JsonKey() List get assignments { + if (_assignments is EqualUnmodifiableListView) return _assignments; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_assignments); +} + + final List _quizzes; +@override@JsonKey() List get quizzes { + if (_quizzes is EqualUnmodifiableListView) return _quizzes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_quizzes); +} + + final List _nodules; +@override@JsonKey() List get nodules { + if (_nodules is EqualUnmodifiableListView) return _nodules; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_nodules); +} + +@override final FillInTheBlank? fillInTheBlank; +@override final Scene? scene; + +/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ChallengeCopyWith<_Challenge> get copyWith => __$ChallengeCopyWithImpl<_Challenge>(this, _$identity); + +@override +Map toJson() { + return _$ChallengeToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Challenge&&(identical(other.id, id) || other.id == id)&&(identical(other.block, block) || other.block == block)&&(identical(other.superBlock, superBlock) || other.superBlock == superBlock)&&(identical(other.title, title) || other.title == title)&&(identical(other.dashedName, dashedName) || other.dashedName == dashedName)&&(identical(other.challengeType, challengeType) || other.challengeType == challengeType)&&(identical(other.helpCategory, helpCategory) || other.helpCategory == helpCategory)&&(identical(other.description, description) || other.description == description)&&(identical(other.instructions, instructions) || other.instructions == instructions)&&(identical(other.explanation, explanation) || other.explanation == explanation)&&(identical(other.transcript, transcript) || other.transcript == transcript)&&(identical(other.videoId, videoId) || other.videoId == videoId)&&const DeepCollectionEquality().equals(other._questions, _questions)&&const DeepCollectionEquality().equals(other._assignments, _assignments)&&const DeepCollectionEquality().equals(other._quizzes, _quizzes)&&const DeepCollectionEquality().equals(other._nodules, _nodules)&&(identical(other.fillInTheBlank, fillInTheBlank) || other.fillInTheBlank == fillInTheBlank)&&(identical(other.scene, scene) || other.scene == scene)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,block,superBlock,title,dashedName,challengeType,helpCategory,description,instructions,explanation,transcript,videoId,const DeepCollectionEquality().hash(_questions),const DeepCollectionEquality().hash(_assignments),const DeepCollectionEquality().hash(_quizzes),const DeepCollectionEquality().hash(_nodules),fillInTheBlank,scene); + +@override +String toString() { + return 'Challenge(id: $id, block: $block, superBlock: $superBlock, title: $title, dashedName: $dashedName, challengeType: $challengeType, helpCategory: $helpCategory, description: $description, instructions: $instructions, explanation: $explanation, transcript: $transcript, videoId: $videoId, questions: $questions, assignments: $assignments, quizzes: $quizzes, nodules: $nodules, fillInTheBlank: $fillInTheBlank, scene: $scene)'; +} + + +} + +/// @nodoc +abstract mixin class _$ChallengeCopyWith<$Res> implements $ChallengeCopyWith<$Res> { + factory _$ChallengeCopyWith(_Challenge value, $Res Function(_Challenge) _then) = __$ChallengeCopyWithImpl; +@override @useResult +$Res call({ + String id, String block, String superBlock, String title, String dashedName, int challengeType, String helpCategory, String description, String instructions, String explanation, String transcript, String? videoId, List questions, List assignments, List quizzes, List nodules, FillInTheBlank? fillInTheBlank, Scene? scene +}); + + +@override $FillInTheBlankCopyWith<$Res>? get fillInTheBlank;@override $SceneCopyWith<$Res>? get scene; + +} +/// @nodoc +class __$ChallengeCopyWithImpl<$Res> + implements _$ChallengeCopyWith<$Res> { + __$ChallengeCopyWithImpl(this._self, this._then); + + final _Challenge _self; + final $Res Function(_Challenge) _then; + +/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? block = null,Object? superBlock = null,Object? title = null,Object? dashedName = null,Object? challengeType = null,Object? helpCategory = null,Object? description = null,Object? instructions = null,Object? explanation = null,Object? transcript = null,Object? videoId = freezed,Object? questions = null,Object? assignments = null,Object? quizzes = null,Object? nodules = null,Object? fillInTheBlank = freezed,Object? scene = freezed,}) { + return _then(_Challenge( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,block: null == block ? _self.block : block // ignore: cast_nullable_to_non_nullable +as String,superBlock: null == superBlock ? _self.superBlock : superBlock // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,dashedName: null == dashedName ? _self.dashedName : dashedName // ignore: cast_nullable_to_non_nullable +as String,challengeType: null == challengeType ? _self.challengeType : challengeType // ignore: cast_nullable_to_non_nullable +as int,helpCategory: null == helpCategory ? _self.helpCategory : helpCategory // ignore: cast_nullable_to_non_nullable +as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String,instructions: null == instructions ? _self.instructions : instructions // ignore: cast_nullable_to_non_nullable +as String,explanation: null == explanation ? _self.explanation : explanation // ignore: cast_nullable_to_non_nullable +as String,transcript: null == transcript ? _self.transcript : transcript // ignore: cast_nullable_to_non_nullable +as String,videoId: freezed == videoId ? _self.videoId : videoId // ignore: cast_nullable_to_non_nullable +as String?,questions: null == questions ? _self._questions : questions // ignore: cast_nullable_to_non_nullable +as List,assignments: null == assignments ? _self._assignments : assignments // ignore: cast_nullable_to_non_nullable +as List,quizzes: null == quizzes ? _self._quizzes : quizzes // ignore: cast_nullable_to_non_nullable +as List,nodules: null == nodules ? _self._nodules : nodules // ignore: cast_nullable_to_non_nullable +as List,fillInTheBlank: freezed == fillInTheBlank ? _self.fillInTheBlank : fillInTheBlank // ignore: cast_nullable_to_non_nullable +as FillInTheBlank?,scene: freezed == scene ? _self.scene : scene // ignore: cast_nullable_to_non_nullable +as Scene?, + )); +} + +/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$FillInTheBlankCopyWith<$Res>? get fillInTheBlank { + if (_self.fillInTheBlank == null) { + return null; + } + + return $FillInTheBlankCopyWith<$Res>(_self.fillInTheBlank!, (value) { + return _then(_self.copyWith(fillInTheBlank: value)); + }); +}/// Create a copy of Challenge +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SceneCopyWith<$Res>? get scene { + if (_self.scene == null) { + return null; + } + + return $SceneCopyWith<$Res>(_self.scene!, (value) { + return _then(_self.copyWith(scene: value)); + }); +} +} + + +/// @nodoc +mixin _$Question { + + String get text; List get answers; int get solution; +/// Create a copy of Question +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$QuestionCopyWith get copyWith => _$QuestionCopyWithImpl(this as Question, _$identity); + + /// Serializes this Question to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Question&&(identical(other.text, text) || other.text == text)&&const DeepCollectionEquality().equals(other.answers, answers)&&(identical(other.solution, solution) || other.solution == solution)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text,const DeepCollectionEquality().hash(answers),solution); + +@override +String toString() { + return 'Question(text: $text, answers: $answers, solution: $solution)'; +} + + +} + +/// @nodoc +abstract mixin class $QuestionCopyWith<$Res> { + factory $QuestionCopyWith(Question value, $Res Function(Question) _then) = _$QuestionCopyWithImpl; +@useResult +$Res call({ + String text, List answers, int solution +}); + + + + +} +/// @nodoc +class _$QuestionCopyWithImpl<$Res> + implements $QuestionCopyWith<$Res> { + _$QuestionCopyWithImpl(this._self, this._then); + + final Question _self; + final $Res Function(Question) _then; + +/// Create a copy of Question +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? answers = null,Object? solution = null,}) { + return _then(_self.copyWith( +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String,answers: null == answers ? _self.answers : answers // ignore: cast_nullable_to_non_nullable +as List,solution: null == solution ? _self.solution : solution // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Question]. +extension QuestionPatterns on Question { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Question value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Question() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Question value) $default,){ +final _that = this; +switch (_that) { +case _Question(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Question value)? $default,){ +final _that = this; +switch (_that) { +case _Question() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String text, List answers, int solution)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Question() when $default != null: +return $default(_that.text,_that.answers,_that.solution);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String text, List answers, int solution) $default,) {final _that = this; +switch (_that) { +case _Question(): +return $default(_that.text,_that.answers,_that.solution);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String text, List answers, int solution)? $default,) {final _that = this; +switch (_that) { +case _Question() when $default != null: +return $default(_that.text,_that.answers,_that.solution);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Question implements Question { + const _Question({required this.text, required final List answers, required this.solution}): _answers = answers; + factory _Question.fromJson(Map json) => _$QuestionFromJson(json); + +@override final String text; + final List _answers; +@override List get answers { + if (_answers is EqualUnmodifiableListView) return _answers; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_answers); +} + +@override final int solution; + +/// Create a copy of Question +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$QuestionCopyWith<_Question> get copyWith => __$QuestionCopyWithImpl<_Question>(this, _$identity); + +@override +Map toJson() { + return _$QuestionToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Question&&(identical(other.text, text) || other.text == text)&&const DeepCollectionEquality().equals(other._answers, _answers)&&(identical(other.solution, solution) || other.solution == solution)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text,const DeepCollectionEquality().hash(_answers),solution); + +@override +String toString() { + return 'Question(text: $text, answers: $answers, solution: $solution)'; +} + + +} + +/// @nodoc +abstract mixin class _$QuestionCopyWith<$Res> implements $QuestionCopyWith<$Res> { + factory _$QuestionCopyWith(_Question value, $Res Function(_Question) _then) = __$QuestionCopyWithImpl; +@override @useResult +$Res call({ + String text, List answers, int solution +}); + + + + +} +/// @nodoc +class __$QuestionCopyWithImpl<$Res> + implements _$QuestionCopyWith<$Res> { + __$QuestionCopyWithImpl(this._self, this._then); + + final _Question _self; + final $Res Function(_Question) _then; + +/// Create a copy of Question +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? answers = null,Object? solution = null,}) { + return _then(_Question( +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String,answers: null == answers ? _self._answers : answers // ignore: cast_nullable_to_non_nullable +as List,solution: null == solution ? _self.solution : solution // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + + +/// @nodoc +mixin _$Answer { + + String get answer; String? get feedback; +/// Create a copy of Answer +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AnswerCopyWith get copyWith => _$AnswerCopyWithImpl(this as Answer, _$identity); + + /// Serializes this Answer to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Answer&&(identical(other.answer, answer) || other.answer == answer)&&(identical(other.feedback, feedback) || other.feedback == feedback)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,answer,feedback); + +@override +String toString() { + return 'Answer(answer: $answer, feedback: $feedback)'; +} + + +} + +/// @nodoc +abstract mixin class $AnswerCopyWith<$Res> { + factory $AnswerCopyWith(Answer value, $Res Function(Answer) _then) = _$AnswerCopyWithImpl; +@useResult +$Res call({ + String answer, String? feedback +}); + + + + +} +/// @nodoc +class _$AnswerCopyWithImpl<$Res> + implements $AnswerCopyWith<$Res> { + _$AnswerCopyWithImpl(this._self, this._then); + + final Answer _self; + final $Res Function(Answer) _then; + +/// Create a copy of Answer +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? answer = null,Object? feedback = freezed,}) { + return _then(_self.copyWith( +answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable +as String,feedback: freezed == feedback ? _self.feedback : feedback // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Answer]. +extension AnswerPatterns on Answer { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Answer value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Answer() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Answer value) $default,){ +final _that = this; +switch (_that) { +case _Answer(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Answer value)? $default,){ +final _that = this; +switch (_that) { +case _Answer() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String answer, String? feedback)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Answer() when $default != null: +return $default(_that.answer,_that.feedback);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String answer, String? feedback) $default,) {final _that = this; +switch (_that) { +case _Answer(): +return $default(_that.answer,_that.feedback);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String answer, String? feedback)? $default,) {final _that = this; +switch (_that) { +case _Answer() when $default != null: +return $default(_that.answer,_that.feedback);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Answer implements Answer { + const _Answer({required this.answer, this.feedback}); + factory _Answer.fromJson(Map json) => _$AnswerFromJson(json); + +@override final String answer; +@override final String? feedback; + +/// Create a copy of Answer +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AnswerCopyWith<_Answer> get copyWith => __$AnswerCopyWithImpl<_Answer>(this, _$identity); + +@override +Map toJson() { + return _$AnswerToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Answer&&(identical(other.answer, answer) || other.answer == answer)&&(identical(other.feedback, feedback) || other.feedback == feedback)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,answer,feedback); + +@override +String toString() { + return 'Answer(answer: $answer, feedback: $feedback)'; +} + + +} + +/// @nodoc +abstract mixin class _$AnswerCopyWith<$Res> implements $AnswerCopyWith<$Res> { + factory _$AnswerCopyWith(_Answer value, $Res Function(_Answer) _then) = __$AnswerCopyWithImpl; +@override @useResult +$Res call({ + String answer, String? feedback +}); + + + + +} +/// @nodoc +class __$AnswerCopyWithImpl<$Res> + implements _$AnswerCopyWith<$Res> { + __$AnswerCopyWithImpl(this._self, this._then); + + final _Answer _self; + final $Res Function(_Answer) _then; + +/// Create a copy of Answer +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? answer = null,Object? feedback = freezed,}) { + return _then(_Answer( +answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable +as String,feedback: freezed == feedback ? _self.feedback : feedback // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$Quiz { + + List get questions; +/// Create a copy of Quiz +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$QuizCopyWith get copyWith => _$QuizCopyWithImpl(this as Quiz, _$identity); + + /// Serializes this Quiz to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Quiz&&const DeepCollectionEquality().equals(other.questions, questions)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(questions)); + +@override +String toString() { + return 'Quiz(questions: $questions)'; +} + + +} + +/// @nodoc +abstract mixin class $QuizCopyWith<$Res> { + factory $QuizCopyWith(Quiz value, $Res Function(Quiz) _then) = _$QuizCopyWithImpl; +@useResult +$Res call({ + List questions +}); + + + + +} +/// @nodoc +class _$QuizCopyWithImpl<$Res> + implements $QuizCopyWith<$Res> { + _$QuizCopyWithImpl(this._self, this._then); + + final Quiz _self; + final $Res Function(Quiz) _then; + +/// Create a copy of Quiz +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? questions = null,}) { + return _then(_self.copyWith( +questions: null == questions ? _self.questions : questions // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Quiz]. +extension QuizPatterns on Quiz { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Quiz value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Quiz() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Quiz value) $default,){ +final _that = this; +switch (_that) { +case _Quiz(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Quiz value)? $default,){ +final _that = this; +switch (_that) { +case _Quiz() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List questions)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Quiz() when $default != null: +return $default(_that.questions);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List questions) $default,) {final _that = this; +switch (_that) { +case _Quiz(): +return $default(_that.questions);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List questions)? $default,) {final _that = this; +switch (_that) { +case _Quiz() when $default != null: +return $default(_that.questions);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Quiz implements Quiz { + const _Quiz({required final List questions}): _questions = questions; + factory _Quiz.fromJson(Map json) => _$QuizFromJson(json); + + final List _questions; +@override List get questions { + if (_questions is EqualUnmodifiableListView) return _questions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_questions); +} + + +/// Create a copy of Quiz +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$QuizCopyWith<_Quiz> get copyWith => __$QuizCopyWithImpl<_Quiz>(this, _$identity); + +@override +Map toJson() { + return _$QuizToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Quiz&&const DeepCollectionEquality().equals(other._questions, _questions)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_questions)); + +@override +String toString() { + return 'Quiz(questions: $questions)'; +} + + +} + +/// @nodoc +abstract mixin class _$QuizCopyWith<$Res> implements $QuizCopyWith<$Res> { + factory _$QuizCopyWith(_Quiz value, $Res Function(_Quiz) _then) = __$QuizCopyWithImpl; +@override @useResult +$Res call({ + List questions +}); + + + + +} +/// @nodoc +class __$QuizCopyWithImpl<$Res> + implements _$QuizCopyWith<$Res> { + __$QuizCopyWithImpl(this._self, this._then); + + final _Quiz _self; + final $Res Function(_Quiz) _then; + +/// Create a copy of Quiz +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? questions = null,}) { + return _then(_Quiz( +questions: null == questions ? _self._questions : questions // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$QuizQuestion { + + String get text; String get answer; List get distractors; QuizAudioData? get audioData; +/// Create a copy of QuizQuestion +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$QuizQuestionCopyWith get copyWith => _$QuizQuestionCopyWithImpl(this as QuizQuestion, _$identity); + + /// Serializes this QuizQuestion to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is QuizQuestion&&(identical(other.text, text) || other.text == text)&&(identical(other.answer, answer) || other.answer == answer)&&const DeepCollectionEquality().equals(other.distractors, distractors)&&(identical(other.audioData, audioData) || other.audioData == audioData)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text,answer,const DeepCollectionEquality().hash(distractors),audioData); + +@override +String toString() { + return 'QuizQuestion(text: $text, answer: $answer, distractors: $distractors, audioData: $audioData)'; +} + + +} + +/// @nodoc +abstract mixin class $QuizQuestionCopyWith<$Res> { + factory $QuizQuestionCopyWith(QuizQuestion value, $Res Function(QuizQuestion) _then) = _$QuizQuestionCopyWithImpl; +@useResult +$Res call({ + String text, String answer, List distractors, QuizAudioData? audioData +}); + + +$QuizAudioDataCopyWith<$Res>? get audioData; + +} +/// @nodoc +class _$QuizQuestionCopyWithImpl<$Res> + implements $QuizQuestionCopyWith<$Res> { + _$QuizQuestionCopyWithImpl(this._self, this._then); + + final QuizQuestion _self; + final $Res Function(QuizQuestion) _then; + +/// Create a copy of QuizQuestion +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? answer = null,Object? distractors = null,Object? audioData = freezed,}) { + return _then(_self.copyWith( +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String,answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable +as String,distractors: null == distractors ? _self.distractors : distractors // ignore: cast_nullable_to_non_nullable +as List,audioData: freezed == audioData ? _self.audioData : audioData // ignore: cast_nullable_to_non_nullable +as QuizAudioData?, + )); +} +/// Create a copy of QuizQuestion +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$QuizAudioDataCopyWith<$Res>? get audioData { + if (_self.audioData == null) { + return null; + } + + return $QuizAudioDataCopyWith<$Res>(_self.audioData!, (value) { + return _then(_self.copyWith(audioData: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [QuizQuestion]. +extension QuizQuestionPatterns on QuizQuestion { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _QuizQuestion value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _QuizQuestion() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _QuizQuestion value) $default,){ +final _that = this; +switch (_that) { +case _QuizQuestion(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _QuizQuestion value)? $default,){ +final _that = this; +switch (_that) { +case _QuizQuestion() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String text, String answer, List distractors, QuizAudioData? audioData)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _QuizQuestion() when $default != null: +return $default(_that.text,_that.answer,_that.distractors,_that.audioData);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String text, String answer, List distractors, QuizAudioData? audioData) $default,) {final _that = this; +switch (_that) { +case _QuizQuestion(): +return $default(_that.text,_that.answer,_that.distractors,_that.audioData);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String text, String answer, List distractors, QuizAudioData? audioData)? $default,) {final _that = this; +switch (_that) { +case _QuizQuestion() when $default != null: +return $default(_that.text,_that.answer,_that.distractors,_that.audioData);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _QuizQuestion implements QuizQuestion { + const _QuizQuestion({required this.text, required this.answer, required final List distractors, this.audioData}): _distractors = distractors; + factory _QuizQuestion.fromJson(Map json) => _$QuizQuestionFromJson(json); + +@override final String text; +@override final String answer; + final List _distractors; +@override List get distractors { + if (_distractors is EqualUnmodifiableListView) return _distractors; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_distractors); +} + +@override final QuizAudioData? audioData; + +/// Create a copy of QuizQuestion +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$QuizQuestionCopyWith<_QuizQuestion> get copyWith => __$QuizQuestionCopyWithImpl<_QuizQuestion>(this, _$identity); + +@override +Map toJson() { + return _$QuizQuestionToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _QuizQuestion&&(identical(other.text, text) || other.text == text)&&(identical(other.answer, answer) || other.answer == answer)&&const DeepCollectionEquality().equals(other._distractors, _distractors)&&(identical(other.audioData, audioData) || other.audioData == audioData)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text,answer,const DeepCollectionEquality().hash(_distractors),audioData); + +@override +String toString() { + return 'QuizQuestion(text: $text, answer: $answer, distractors: $distractors, audioData: $audioData)'; +} + + +} + +/// @nodoc +abstract mixin class _$QuizQuestionCopyWith<$Res> implements $QuizQuestionCopyWith<$Res> { + factory _$QuizQuestionCopyWith(_QuizQuestion value, $Res Function(_QuizQuestion) _then) = __$QuizQuestionCopyWithImpl; +@override @useResult +$Res call({ + String text, String answer, List distractors, QuizAudioData? audioData +}); + + +@override $QuizAudioDataCopyWith<$Res>? get audioData; + +} +/// @nodoc +class __$QuizQuestionCopyWithImpl<$Res> + implements _$QuizQuestionCopyWith<$Res> { + __$QuizQuestionCopyWithImpl(this._self, this._then); + + final _QuizQuestion _self; + final $Res Function(_QuizQuestion) _then; + +/// Create a copy of QuizQuestion +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? answer = null,Object? distractors = null,Object? audioData = freezed,}) { + return _then(_QuizQuestion( +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String,answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable +as String,distractors: null == distractors ? _self._distractors : distractors // ignore: cast_nullable_to_non_nullable +as List,audioData: freezed == audioData ? _self.audioData : audioData // ignore: cast_nullable_to_non_nullable +as QuizAudioData?, + )); +} + +/// Create a copy of QuizQuestion +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$QuizAudioDataCopyWith<$Res>? get audioData { + if (_self.audioData == null) { + return null; + } + + return $QuizAudioDataCopyWith<$Res>(_self.audioData!, (value) { + return _then(_self.copyWith(audioData: value)); + }); +} +} + + +/// @nodoc +mixin _$QuizAudioData { + + AudioClip get audio; List get transcript; +/// Create a copy of QuizAudioData +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$QuizAudioDataCopyWith get copyWith => _$QuizAudioDataCopyWithImpl(this as QuizAudioData, _$identity); + + /// Serializes this QuizAudioData to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is QuizAudioData&&(identical(other.audio, audio) || other.audio == audio)&&const DeepCollectionEquality().equals(other.transcript, transcript)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,audio,const DeepCollectionEquality().hash(transcript)); + +@override +String toString() { + return 'QuizAudioData(audio: $audio, transcript: $transcript)'; +} + + +} + +/// @nodoc +abstract mixin class $QuizAudioDataCopyWith<$Res> { + factory $QuizAudioDataCopyWith(QuizAudioData value, $Res Function(QuizAudioData) _then) = _$QuizAudioDataCopyWithImpl; +@useResult +$Res call({ + AudioClip audio, List transcript +}); + + +$AudioClipCopyWith<$Res> get audio; + +} +/// @nodoc +class _$QuizAudioDataCopyWithImpl<$Res> + implements $QuizAudioDataCopyWith<$Res> { + _$QuizAudioDataCopyWithImpl(this._self, this._then); + + final QuizAudioData _self; + final $Res Function(QuizAudioData) _then; + +/// Create a copy of QuizAudioData +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? audio = null,Object? transcript = null,}) { + return _then(_self.copyWith( +audio: null == audio ? _self.audio : audio // ignore: cast_nullable_to_non_nullable +as AudioClip,transcript: null == transcript ? _self.transcript : transcript // ignore: cast_nullable_to_non_nullable +as List, + )); +} +/// Create a copy of QuizAudioData +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AudioClipCopyWith<$Res> get audio { + + return $AudioClipCopyWith<$Res>(_self.audio, (value) { + return _then(_self.copyWith(audio: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [QuizAudioData]. +extension QuizAudioDataPatterns on QuizAudioData { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _QuizAudioData value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _QuizAudioData() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _QuizAudioData value) $default,){ +final _that = this; +switch (_that) { +case _QuizAudioData(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _QuizAudioData value)? $default,){ +final _that = this; +switch (_that) { +case _QuizAudioData() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( AudioClip audio, List transcript)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _QuizAudioData() when $default != null: +return $default(_that.audio,_that.transcript);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( AudioClip audio, List transcript) $default,) {final _that = this; +switch (_that) { +case _QuizAudioData(): +return $default(_that.audio,_that.transcript);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( AudioClip audio, List transcript)? $default,) {final _that = this; +switch (_that) { +case _QuizAudioData() when $default != null: +return $default(_that.audio,_that.transcript);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _QuizAudioData implements QuizAudioData { + const _QuizAudioData({required this.audio, final List transcript = const []}): _transcript = transcript; + factory _QuizAudioData.fromJson(Map json) => _$QuizAudioDataFromJson(json); + +@override final AudioClip audio; + final List _transcript; +@override@JsonKey() List get transcript { + if (_transcript is EqualUnmodifiableListView) return _transcript; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_transcript); +} + + +/// Create a copy of QuizAudioData +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$QuizAudioDataCopyWith<_QuizAudioData> get copyWith => __$QuizAudioDataCopyWithImpl<_QuizAudioData>(this, _$identity); + +@override +Map toJson() { + return _$QuizAudioDataToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _QuizAudioData&&(identical(other.audio, audio) || other.audio == audio)&&const DeepCollectionEquality().equals(other._transcript, _transcript)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,audio,const DeepCollectionEquality().hash(_transcript)); + +@override +String toString() { + return 'QuizAudioData(audio: $audio, transcript: $transcript)'; +} + + +} + +/// @nodoc +abstract mixin class _$QuizAudioDataCopyWith<$Res> implements $QuizAudioDataCopyWith<$Res> { + factory _$QuizAudioDataCopyWith(_QuizAudioData value, $Res Function(_QuizAudioData) _then) = __$QuizAudioDataCopyWithImpl; +@override @useResult +$Res call({ + AudioClip audio, List transcript +}); + + +@override $AudioClipCopyWith<$Res> get audio; + +} +/// @nodoc +class __$QuizAudioDataCopyWithImpl<$Res> + implements _$QuizAudioDataCopyWith<$Res> { + __$QuizAudioDataCopyWithImpl(this._self, this._then); + + final _QuizAudioData _self; + final $Res Function(_QuizAudioData) _then; + +/// Create a copy of QuizAudioData +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? audio = null,Object? transcript = null,}) { + return _then(_QuizAudioData( +audio: null == audio ? _self.audio : audio // ignore: cast_nullable_to_non_nullable +as AudioClip,transcript: null == transcript ? _self._transcript : transcript // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +/// Create a copy of QuizAudioData +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AudioClipCopyWith<$Res> get audio { + + return $AudioClipCopyWith<$Res>(_self.audio, (value) { + return _then(_self.copyWith(audio: value)); + }); +} +} + + +/// @nodoc +mixin _$TranscriptLine { + + String get character; String get text; +/// Create a copy of TranscriptLine +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$TranscriptLineCopyWith get copyWith => _$TranscriptLineCopyWithImpl(this as TranscriptLine, _$identity); + + /// Serializes this TranscriptLine to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is TranscriptLine&&(identical(other.character, character) || other.character == character)&&(identical(other.text, text) || other.text == text)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,character,text); + +@override +String toString() { + return 'TranscriptLine(character: $character, text: $text)'; +} + + +} + +/// @nodoc +abstract mixin class $TranscriptLineCopyWith<$Res> { + factory $TranscriptLineCopyWith(TranscriptLine value, $Res Function(TranscriptLine) _then) = _$TranscriptLineCopyWithImpl; +@useResult +$Res call({ + String character, String text +}); + + + + +} +/// @nodoc +class _$TranscriptLineCopyWithImpl<$Res> + implements $TranscriptLineCopyWith<$Res> { + _$TranscriptLineCopyWithImpl(this._self, this._then); + + final TranscriptLine _self; + final $Res Function(TranscriptLine) _then; + +/// Create a copy of TranscriptLine +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? character = null,Object? text = null,}) { + return _then(_self.copyWith( +character: null == character ? _self.character : character // ignore: cast_nullable_to_non_nullable +as String,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [TranscriptLine]. +extension TranscriptLinePatterns on TranscriptLine { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _TranscriptLine value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _TranscriptLine() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _TranscriptLine value) $default,){ +final _that = this; +switch (_that) { +case _TranscriptLine(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _TranscriptLine value)? $default,){ +final _that = this; +switch (_that) { +case _TranscriptLine() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String character, String text)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _TranscriptLine() when $default != null: +return $default(_that.character,_that.text);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String character, String text) $default,) {final _that = this; +switch (_that) { +case _TranscriptLine(): +return $default(_that.character,_that.text);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String character, String text)? $default,) {final _that = this; +switch (_that) { +case _TranscriptLine() when $default != null: +return $default(_that.character,_that.text);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _TranscriptLine implements TranscriptLine { + const _TranscriptLine({required this.character, required this.text}); + factory _TranscriptLine.fromJson(Map json) => _$TranscriptLineFromJson(json); + +@override final String character; +@override final String text; + +/// Create a copy of TranscriptLine +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$TranscriptLineCopyWith<_TranscriptLine> get copyWith => __$TranscriptLineCopyWithImpl<_TranscriptLine>(this, _$identity); + +@override +Map toJson() { + return _$TranscriptLineToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _TranscriptLine&&(identical(other.character, character) || other.character == character)&&(identical(other.text, text) || other.text == text)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,character,text); + +@override +String toString() { + return 'TranscriptLine(character: $character, text: $text)'; +} + + +} + +/// @nodoc +abstract mixin class _$TranscriptLineCopyWith<$Res> implements $TranscriptLineCopyWith<$Res> { + factory _$TranscriptLineCopyWith(_TranscriptLine value, $Res Function(_TranscriptLine) _then) = __$TranscriptLineCopyWithImpl; +@override @useResult +$Res call({ + String character, String text +}); + + + + +} +/// @nodoc +class __$TranscriptLineCopyWithImpl<$Res> + implements _$TranscriptLineCopyWith<$Res> { + __$TranscriptLineCopyWithImpl(this._self, this._then); + + final _TranscriptLine _self; + final $Res Function(_TranscriptLine) _then; + +/// Create a copy of TranscriptLine +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? character = null,Object? text = null,}) { + return _then(_TranscriptLine( +character: null == character ? _self.character : character // ignore: cast_nullable_to_non_nullable +as String,text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$AudioClip { + + String get filename; num get startTime; num? get startTimestamp; num? get finishTimestamp; +/// Create a copy of AudioClip +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AudioClipCopyWith get copyWith => _$AudioClipCopyWithImpl(this as AudioClip, _$identity); + + /// Serializes this AudioClip to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AudioClip&&(identical(other.filename, filename) || other.filename == filename)&&(identical(other.startTime, startTime) || other.startTime == startTime)&&(identical(other.startTimestamp, startTimestamp) || other.startTimestamp == startTimestamp)&&(identical(other.finishTimestamp, finishTimestamp) || other.finishTimestamp == finishTimestamp)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,filename,startTime,startTimestamp,finishTimestamp); + +@override +String toString() { + return 'AudioClip(filename: $filename, startTime: $startTime, startTimestamp: $startTimestamp, finishTimestamp: $finishTimestamp)'; +} + + +} + +/// @nodoc +abstract mixin class $AudioClipCopyWith<$Res> { + factory $AudioClipCopyWith(AudioClip value, $Res Function(AudioClip) _then) = _$AudioClipCopyWithImpl; +@useResult +$Res call({ + String filename, num startTime, num? startTimestamp, num? finishTimestamp +}); + + + + +} +/// @nodoc +class _$AudioClipCopyWithImpl<$Res> + implements $AudioClipCopyWith<$Res> { + _$AudioClipCopyWithImpl(this._self, this._then); + + final AudioClip _self; + final $Res Function(AudioClip) _then; + +/// Create a copy of AudioClip +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? filename = null,Object? startTime = null,Object? startTimestamp = freezed,Object? finishTimestamp = freezed,}) { + return _then(_self.copyWith( +filename: null == filename ? _self.filename : filename // ignore: cast_nullable_to_non_nullable +as String,startTime: null == startTime ? _self.startTime : startTime // ignore: cast_nullable_to_non_nullable +as num,startTimestamp: freezed == startTimestamp ? _self.startTimestamp : startTimestamp // ignore: cast_nullable_to_non_nullable +as num?,finishTimestamp: freezed == finishTimestamp ? _self.finishTimestamp : finishTimestamp // ignore: cast_nullable_to_non_nullable +as num?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [AudioClip]. +extension AudioClipPatterns on AudioClip { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _AudioClip value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AudioClip() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _AudioClip value) $default,){ +final _that = this; +switch (_that) { +case _AudioClip(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AudioClip value)? $default,){ +final _that = this; +switch (_that) { +case _AudioClip() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String filename, num startTime, num? startTimestamp, num? finishTimestamp)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AudioClip() when $default != null: +return $default(_that.filename,_that.startTime,_that.startTimestamp,_that.finishTimestamp);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String filename, num startTime, num? startTimestamp, num? finishTimestamp) $default,) {final _that = this; +switch (_that) { +case _AudioClip(): +return $default(_that.filename,_that.startTime,_that.startTimestamp,_that.finishTimestamp);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String filename, num startTime, num? startTimestamp, num? finishTimestamp)? $default,) {final _that = this; +switch (_that) { +case _AudioClip() when $default != null: +return $default(_that.filename,_that.startTime,_that.startTimestamp,_that.finishTimestamp);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _AudioClip extends AudioClip { + const _AudioClip({required this.filename, this.startTime = 0, this.startTimestamp, this.finishTimestamp}): super._(); + factory _AudioClip.fromJson(Map json) => _$AudioClipFromJson(json); + +@override final String filename; +@override@JsonKey() final num startTime; +@override final num? startTimestamp; +@override final num? finishTimestamp; + +/// Create a copy of AudioClip +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AudioClipCopyWith<_AudioClip> get copyWith => __$AudioClipCopyWithImpl<_AudioClip>(this, _$identity); + +@override +Map toJson() { + return _$AudioClipToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AudioClip&&(identical(other.filename, filename) || other.filename == filename)&&(identical(other.startTime, startTime) || other.startTime == startTime)&&(identical(other.startTimestamp, startTimestamp) || other.startTimestamp == startTimestamp)&&(identical(other.finishTimestamp, finishTimestamp) || other.finishTimestamp == finishTimestamp)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,filename,startTime,startTimestamp,finishTimestamp); + +@override +String toString() { + return 'AudioClip(filename: $filename, startTime: $startTime, startTimestamp: $startTimestamp, finishTimestamp: $finishTimestamp)'; +} + + +} + +/// @nodoc +abstract mixin class _$AudioClipCopyWith<$Res> implements $AudioClipCopyWith<$Res> { + factory _$AudioClipCopyWith(_AudioClip value, $Res Function(_AudioClip) _then) = __$AudioClipCopyWithImpl; +@override @useResult +$Res call({ + String filename, num startTime, num? startTimestamp, num? finishTimestamp +}); + + + + +} +/// @nodoc +class __$AudioClipCopyWithImpl<$Res> + implements _$AudioClipCopyWith<$Res> { + __$AudioClipCopyWithImpl(this._self, this._then); + + final _AudioClip _self; + final $Res Function(_AudioClip) _then; + +/// Create a copy of AudioClip +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? filename = null,Object? startTime = null,Object? startTimestamp = freezed,Object? finishTimestamp = freezed,}) { + return _then(_AudioClip( +filename: null == filename ? _self.filename : filename // ignore: cast_nullable_to_non_nullable +as String,startTime: null == startTime ? _self.startTime : startTime // ignore: cast_nullable_to_non_nullable +as num,startTimestamp: freezed == startTimestamp ? _self.startTimestamp : startTimestamp // ignore: cast_nullable_to_non_nullable +as num?,finishTimestamp: freezed == finishTimestamp ? _self.finishTimestamp : finishTimestamp // ignore: cast_nullable_to_non_nullable +as num?, + )); +} + + +} + + +/// @nodoc +mixin _$FillInTheBlank { + + String get sentence; List get blanks; +/// Create a copy of FillInTheBlank +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$FillInTheBlankCopyWith get copyWith => _$FillInTheBlankCopyWithImpl(this as FillInTheBlank, _$identity); + + /// Serializes this FillInTheBlank to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is FillInTheBlank&&(identical(other.sentence, sentence) || other.sentence == sentence)&&const DeepCollectionEquality().equals(other.blanks, blanks)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,sentence,const DeepCollectionEquality().hash(blanks)); + +@override +String toString() { + return 'FillInTheBlank(sentence: $sentence, blanks: $blanks)'; +} + + +} + +/// @nodoc +abstract mixin class $FillInTheBlankCopyWith<$Res> { + factory $FillInTheBlankCopyWith(FillInTheBlank value, $Res Function(FillInTheBlank) _then) = _$FillInTheBlankCopyWithImpl; +@useResult +$Res call({ + String sentence, List blanks +}); + + + + +} +/// @nodoc +class _$FillInTheBlankCopyWithImpl<$Res> + implements $FillInTheBlankCopyWith<$Res> { + _$FillInTheBlankCopyWithImpl(this._self, this._then); + + final FillInTheBlank _self; + final $Res Function(FillInTheBlank) _then; + +/// Create a copy of FillInTheBlank +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? sentence = null,Object? blanks = null,}) { + return _then(_self.copyWith( +sentence: null == sentence ? _self.sentence : sentence // ignore: cast_nullable_to_non_nullable +as String,blanks: null == blanks ? _self.blanks : blanks // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [FillInTheBlank]. +extension FillInTheBlankPatterns on FillInTheBlank { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _FillInTheBlank value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _FillInTheBlank() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _FillInTheBlank value) $default,){ +final _that = this; +switch (_that) { +case _FillInTheBlank(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _FillInTheBlank value)? $default,){ +final _that = this; +switch (_that) { +case _FillInTheBlank() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String sentence, List blanks)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _FillInTheBlank() when $default != null: +return $default(_that.sentence,_that.blanks);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String sentence, List blanks) $default,) {final _that = this; +switch (_that) { +case _FillInTheBlank(): +return $default(_that.sentence,_that.blanks);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String sentence, List blanks)? $default,) {final _that = this; +switch (_that) { +case _FillInTheBlank() when $default != null: +return $default(_that.sentence,_that.blanks);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _FillInTheBlank implements FillInTheBlank { + const _FillInTheBlank({required this.sentence, required final List blanks}): _blanks = blanks; + factory _FillInTheBlank.fromJson(Map json) => _$FillInTheBlankFromJson(json); + +@override final String sentence; + final List _blanks; +@override List get blanks { + if (_blanks is EqualUnmodifiableListView) return _blanks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_blanks); +} + + +/// Create a copy of FillInTheBlank +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$FillInTheBlankCopyWith<_FillInTheBlank> get copyWith => __$FillInTheBlankCopyWithImpl<_FillInTheBlank>(this, _$identity); + +@override +Map toJson() { + return _$FillInTheBlankToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _FillInTheBlank&&(identical(other.sentence, sentence) || other.sentence == sentence)&&const DeepCollectionEquality().equals(other._blanks, _blanks)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,sentence,const DeepCollectionEquality().hash(_blanks)); + +@override +String toString() { + return 'FillInTheBlank(sentence: $sentence, blanks: $blanks)'; +} + + +} + +/// @nodoc +abstract mixin class _$FillInTheBlankCopyWith<$Res> implements $FillInTheBlankCopyWith<$Res> { + factory _$FillInTheBlankCopyWith(_FillInTheBlank value, $Res Function(_FillInTheBlank) _then) = __$FillInTheBlankCopyWithImpl; +@override @useResult +$Res call({ + String sentence, List blanks +}); + + + + +} +/// @nodoc +class __$FillInTheBlankCopyWithImpl<$Res> + implements _$FillInTheBlankCopyWith<$Res> { + __$FillInTheBlankCopyWithImpl(this._self, this._then); + + final _FillInTheBlank _self; + final $Res Function(_FillInTheBlank) _then; + +/// Create a copy of FillInTheBlank +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? sentence = null,Object? blanks = null,}) { + return _then(_FillInTheBlank( +sentence: null == sentence ? _self.sentence : sentence // ignore: cast_nullable_to_non_nullable +as String,blanks: null == blanks ? _self._blanks : blanks // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$Blank { + + String get answer; String get feedback; +/// Create a copy of Blank +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$BlankCopyWith get copyWith => _$BlankCopyWithImpl(this as Blank, _$identity); + + /// Serializes this Blank to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Blank&&(identical(other.answer, answer) || other.answer == answer)&&(identical(other.feedback, feedback) || other.feedback == feedback)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,answer,feedback); + +@override +String toString() { + return 'Blank(answer: $answer, feedback: $feedback)'; +} + + +} + +/// @nodoc +abstract mixin class $BlankCopyWith<$Res> { + factory $BlankCopyWith(Blank value, $Res Function(Blank) _then) = _$BlankCopyWithImpl; +@useResult +$Res call({ + String answer, String feedback +}); + + + + +} +/// @nodoc +class _$BlankCopyWithImpl<$Res> + implements $BlankCopyWith<$Res> { + _$BlankCopyWithImpl(this._self, this._then); + + final Blank _self; + final $Res Function(Blank) _then; + +/// Create a copy of Blank +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? answer = null,Object? feedback = null,}) { + return _then(_self.copyWith( +answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable +as String,feedback: null == feedback ? _self.feedback : feedback // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [Blank]. +extension BlankPatterns on Blank { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Blank value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Blank() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Blank value) $default,){ +final _that = this; +switch (_that) { +case _Blank(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Blank value)? $default,){ +final _that = this; +switch (_that) { +case _Blank() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String answer, String feedback)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Blank() when $default != null: +return $default(_that.answer,_that.feedback);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String answer, String feedback) $default,) {final _that = this; +switch (_that) { +case _Blank(): +return $default(_that.answer,_that.feedback);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String answer, String feedback)? $default,) {final _that = this; +switch (_that) { +case _Blank() when $default != null: +return $default(_that.answer,_that.feedback);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Blank implements Blank { + const _Blank({required this.answer, this.feedback = ''}); + factory _Blank.fromJson(Map json) => _$BlankFromJson(json); + +@override final String answer; +@override@JsonKey() final String feedback; + +/// Create a copy of Blank +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$BlankCopyWith<_Blank> get copyWith => __$BlankCopyWithImpl<_Blank>(this, _$identity); + +@override +Map toJson() { + return _$BlankToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Blank&&(identical(other.answer, answer) || other.answer == answer)&&(identical(other.feedback, feedback) || other.feedback == feedback)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,answer,feedback); + +@override +String toString() { + return 'Blank(answer: $answer, feedback: $feedback)'; +} + + +} + +/// @nodoc +abstract mixin class _$BlankCopyWith<$Res> implements $BlankCopyWith<$Res> { + factory _$BlankCopyWith(_Blank value, $Res Function(_Blank) _then) = __$BlankCopyWithImpl; +@override @useResult +$Res call({ + String answer, String feedback +}); + + + + +} +/// @nodoc +class __$BlankCopyWithImpl<$Res> + implements _$BlankCopyWith<$Res> { + __$BlankCopyWithImpl(this._self, this._then); + + final _Blank _self; + final $Res Function(_Blank) _then; + +/// Create a copy of Blank +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? answer = null,Object? feedback = null,}) { + return _then(_Blank( +answer: null == answer ? _self.answer : answer // ignore: cast_nullable_to_non_nullable +as String,feedback: null == feedback ? _self.feedback : feedback // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +Nodule _$NoduleFromJson( + Map json +) { + switch (json['type']) { + case 'paragraph': + return ParagraphNodule.fromJson( + json + ); + case 'interactiveEditor': + return InteractiveEditorNodule.fromJson( + json + ); + + default: + throw CheckedFromJsonException( + json, + 'type', + 'Nodule', + 'Invalid union type "${json['type']}"!' +); + } + +} + +/// @nodoc +mixin _$Nodule { + + + + /// Serializes this Nodule to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Nodule); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'Nodule()'; +} + + +} + +/// @nodoc +class $NoduleCopyWith<$Res> { +$NoduleCopyWith(Nodule _, $Res Function(Nodule) __); +} + + +/// Adds pattern-matching-related methods to [Nodule]. +extension NodulePatterns on Nodule { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( ParagraphNodule value)? paragraph,TResult Function( InteractiveEditorNodule value)? interactiveEditor,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case ParagraphNodule() when paragraph != null: +return paragraph(_that);case InteractiveEditorNodule() when interactiveEditor != null: +return interactiveEditor(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( ParagraphNodule value) paragraph,required TResult Function( InteractiveEditorNodule value) interactiveEditor,}){ +final _that = this; +switch (_that) { +case ParagraphNodule(): +return paragraph(_that);case InteractiveEditorNodule(): +return interactiveEditor(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( ParagraphNodule value)? paragraph,TResult? Function( InteractiveEditorNodule value)? interactiveEditor,}){ +final _that = this; +switch (_that) { +case ParagraphNodule() when paragraph != null: +return paragraph(_that);case InteractiveEditorNodule() when interactiveEditor != null: +return interactiveEditor(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function(@JsonKey(readValue: _readNoduleContents) String contents)? paragraph,TResult Function(@JsonKey(readValue: _readNoduleContents) List files)? interactiveEditor,required TResult orElse(),}) {final _that = this; +switch (_that) { +case ParagraphNodule() when paragraph != null: +return paragraph(_that.contents);case InteractiveEditorNodule() when interactiveEditor != null: +return interactiveEditor(_that.files);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function(@JsonKey(readValue: _readNoduleContents) String contents) paragraph,required TResult Function(@JsonKey(readValue: _readNoduleContents) List files) interactiveEditor,}) {final _that = this; +switch (_that) { +case ParagraphNodule(): +return paragraph(_that.contents);case InteractiveEditorNodule(): +return interactiveEditor(_that.files);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function(@JsonKey(readValue: _readNoduleContents) String contents)? paragraph,TResult? Function(@JsonKey(readValue: _readNoduleContents) List files)? interactiveEditor,}) {final _that = this; +switch (_that) { +case ParagraphNodule() when paragraph != null: +return paragraph(_that.contents);case InteractiveEditorNodule() when interactiveEditor != null: +return interactiveEditor(_that.files);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class ParagraphNodule implements Nodule { + const ParagraphNodule({@JsonKey(readValue: _readNoduleContents) required this.contents, final String? $type}): $type = $type ?? 'paragraph'; + factory ParagraphNodule.fromJson(Map json) => _$ParagraphNoduleFromJson(json); + +@JsonKey(readValue: _readNoduleContents) final String contents; + +@JsonKey(name: 'type') +final String $type; + + +/// Create a copy of Nodule +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ParagraphNoduleCopyWith get copyWith => _$ParagraphNoduleCopyWithImpl(this, _$identity); + +@override +Map toJson() { + return _$ParagraphNoduleToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ParagraphNodule&&(identical(other.contents, contents) || other.contents == contents)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,contents); + +@override +String toString() { + return 'Nodule.paragraph(contents: $contents)'; +} + + +} + +/// @nodoc +abstract mixin class $ParagraphNoduleCopyWith<$Res> implements $NoduleCopyWith<$Res> { + factory $ParagraphNoduleCopyWith(ParagraphNodule value, $Res Function(ParagraphNodule) _then) = _$ParagraphNoduleCopyWithImpl; +@useResult +$Res call({ +@JsonKey(readValue: _readNoduleContents) String contents +}); + + + + +} +/// @nodoc +class _$ParagraphNoduleCopyWithImpl<$Res> + implements $ParagraphNoduleCopyWith<$Res> { + _$ParagraphNoduleCopyWithImpl(this._self, this._then); + + final ParagraphNodule _self; + final $Res Function(ParagraphNodule) _then; + +/// Create a copy of Nodule +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? contents = null,}) { + return _then(ParagraphNodule( +contents: null == contents ? _self.contents : contents // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc +@JsonSerializable() + +class InteractiveEditorNodule implements Nodule { + const InteractiveEditorNodule({@JsonKey(readValue: _readNoduleContents) required final List files, final String? $type}): _files = files,$type = $type ?? 'interactiveEditor'; + factory InteractiveEditorNodule.fromJson(Map json) => _$InteractiveEditorNoduleFromJson(json); + + final List _files; +@JsonKey(readValue: _readNoduleContents) List get files { + if (_files is EqualUnmodifiableListView) return _files; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_files); +} + + +@JsonKey(name: 'type') +final String $type; + + +/// Create a copy of Nodule +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$InteractiveEditorNoduleCopyWith get copyWith => _$InteractiveEditorNoduleCopyWithImpl(this, _$identity); + +@override +Map toJson() { + return _$InteractiveEditorNoduleToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is InteractiveEditorNodule&&const DeepCollectionEquality().equals(other._files, _files)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_files)); + +@override +String toString() { + return 'Nodule.interactiveEditor(files: $files)'; +} + + +} + +/// @nodoc +abstract mixin class $InteractiveEditorNoduleCopyWith<$Res> implements $NoduleCopyWith<$Res> { + factory $InteractiveEditorNoduleCopyWith(InteractiveEditorNodule value, $Res Function(InteractiveEditorNodule) _then) = _$InteractiveEditorNoduleCopyWithImpl; +@useResult +$Res call({ +@JsonKey(readValue: _readNoduleContents) List files +}); + + + + +} +/// @nodoc +class _$InteractiveEditorNoduleCopyWithImpl<$Res> + implements $InteractiveEditorNoduleCopyWith<$Res> { + _$InteractiveEditorNoduleCopyWithImpl(this._self, this._then); + + final InteractiveEditorNodule _self; + final $Res Function(InteractiveEditorNodule) _then; + +/// Create a copy of Nodule +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? files = null,}) { + return _then(InteractiveEditorNodule( +files: null == files ? _self._files : files // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$NoduleFile { + + String get name; String get ext; String get contents; +/// Create a copy of NoduleFile +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$NoduleFileCopyWith get copyWith => _$NoduleFileCopyWithImpl(this as NoduleFile, _$identity); + + /// Serializes this NoduleFile to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is NoduleFile&&(identical(other.name, name) || other.name == name)&&(identical(other.ext, ext) || other.ext == ext)&&(identical(other.contents, contents) || other.contents == contents)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,ext,contents); + +@override +String toString() { + return 'NoduleFile(name: $name, ext: $ext, contents: $contents)'; +} + + +} + +/// @nodoc +abstract mixin class $NoduleFileCopyWith<$Res> { + factory $NoduleFileCopyWith(NoduleFile value, $Res Function(NoduleFile) _then) = _$NoduleFileCopyWithImpl; +@useResult +$Res call({ + String name, String ext, String contents +}); + + + + +} +/// @nodoc +class _$NoduleFileCopyWithImpl<$Res> + implements $NoduleFileCopyWith<$Res> { + _$NoduleFileCopyWithImpl(this._self, this._then); + + final NoduleFile _self; + final $Res Function(NoduleFile) _then; + +/// Create a copy of NoduleFile +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? ext = null,Object? contents = null,}) { + return _then(_self.copyWith( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,ext: null == ext ? _self.ext : ext // ignore: cast_nullable_to_non_nullable +as String,contents: null == contents ? _self.contents : contents // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [NoduleFile]. +extension NoduleFilePatterns on NoduleFile { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _NoduleFile value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _NoduleFile() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _NoduleFile value) $default,){ +final _that = this; +switch (_that) { +case _NoduleFile(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _NoduleFile value)? $default,){ +final _that = this; +switch (_that) { +case _NoduleFile() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String name, String ext, String contents)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _NoduleFile() when $default != null: +return $default(_that.name,_that.ext,_that.contents);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String name, String ext, String contents) $default,) {final _that = this; +switch (_that) { +case _NoduleFile(): +return $default(_that.name,_that.ext,_that.contents);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String name, String ext, String contents)? $default,) {final _that = this; +switch (_that) { +case _NoduleFile() when $default != null: +return $default(_that.name,_that.ext,_that.contents);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _NoduleFile implements NoduleFile { + const _NoduleFile({required this.name, required this.ext, required this.contents}); + factory _NoduleFile.fromJson(Map json) => _$NoduleFileFromJson(json); + +@override final String name; +@override final String ext; +@override final String contents; + +/// Create a copy of NoduleFile +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$NoduleFileCopyWith<_NoduleFile> get copyWith => __$NoduleFileCopyWithImpl<_NoduleFile>(this, _$identity); + +@override +Map toJson() { + return _$NoduleFileToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _NoduleFile&&(identical(other.name, name) || other.name == name)&&(identical(other.ext, ext) || other.ext == ext)&&(identical(other.contents, contents) || other.contents == contents)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,ext,contents); + +@override +String toString() { + return 'NoduleFile(name: $name, ext: $ext, contents: $contents)'; +} + + +} + +/// @nodoc +abstract mixin class _$NoduleFileCopyWith<$Res> implements $NoduleFileCopyWith<$Res> { + factory _$NoduleFileCopyWith(_NoduleFile value, $Res Function(_NoduleFile) _then) = __$NoduleFileCopyWithImpl; +@override @useResult +$Res call({ + String name, String ext, String contents +}); + + + + +} +/// @nodoc +class __$NoduleFileCopyWithImpl<$Res> + implements _$NoduleFileCopyWith<$Res> { + __$NoduleFileCopyWithImpl(this._self, this._then); + + final _NoduleFile _self; + final $Res Function(_NoduleFile) _then; + +/// Create a copy of NoduleFile +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? ext = null,Object? contents = null,}) { + return _then(_NoduleFile( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,ext: null == ext ? _self.ext : ext // ignore: cast_nullable_to_non_nullable +as String,contents: null == contents ? _self.contents : contents // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$Scene { + + SceneSetup get setup; List get commands; +/// Create a copy of Scene +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SceneCopyWith get copyWith => _$SceneCopyWithImpl(this as Scene, _$identity); + + /// Serializes this Scene to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is Scene&&(identical(other.setup, setup) || other.setup == setup)&&const DeepCollectionEquality().equals(other.commands, commands)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,setup,const DeepCollectionEquality().hash(commands)); + +@override +String toString() { + return 'Scene(setup: $setup, commands: $commands)'; +} + + +} + +/// @nodoc +abstract mixin class $SceneCopyWith<$Res> { + factory $SceneCopyWith(Scene value, $Res Function(Scene) _then) = _$SceneCopyWithImpl; +@useResult +$Res call({ + SceneSetup setup, List commands +}); + + +$SceneSetupCopyWith<$Res> get setup; + +} +/// @nodoc +class _$SceneCopyWithImpl<$Res> + implements $SceneCopyWith<$Res> { + _$SceneCopyWithImpl(this._self, this._then); + + final Scene _self; + final $Res Function(Scene) _then; + +/// Create a copy of Scene +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? setup = null,Object? commands = null,}) { + return _then(_self.copyWith( +setup: null == setup ? _self.setup : setup // ignore: cast_nullable_to_non_nullable +as SceneSetup,commands: null == commands ? _self.commands : commands // ignore: cast_nullable_to_non_nullable +as List, + )); +} +/// Create a copy of Scene +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SceneSetupCopyWith<$Res> get setup { + + return $SceneSetupCopyWith<$Res>(_self.setup, (value) { + return _then(_self.copyWith(setup: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [Scene]. +extension ScenePatterns on Scene { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _Scene value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _Scene() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _Scene value) $default,){ +final _that = this; +switch (_that) { +case _Scene(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _Scene value)? $default,){ +final _that = this; +switch (_that) { +case _Scene() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( SceneSetup setup, List commands)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _Scene() when $default != null: +return $default(_that.setup,_that.commands);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( SceneSetup setup, List commands) $default,) {final _that = this; +switch (_that) { +case _Scene(): +return $default(_that.setup,_that.commands);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( SceneSetup setup, List commands)? $default,) {final _that = this; +switch (_that) { +case _Scene() when $default != null: +return $default(_that.setup,_that.commands);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _Scene implements Scene { + const _Scene({required this.setup, required final List commands}): _commands = commands; + factory _Scene.fromJson(Map json) => _$SceneFromJson(json); + +@override final SceneSetup setup; + final List _commands; +@override List get commands { + if (_commands is EqualUnmodifiableListView) return _commands; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_commands); +} + + +/// Create a copy of Scene +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SceneCopyWith<_Scene> get copyWith => __$SceneCopyWithImpl<_Scene>(this, _$identity); + +@override +Map toJson() { + return _$SceneToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Scene&&(identical(other.setup, setup) || other.setup == setup)&&const DeepCollectionEquality().equals(other._commands, _commands)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,setup,const DeepCollectionEquality().hash(_commands)); + +@override +String toString() { + return 'Scene(setup: $setup, commands: $commands)'; +} + + +} + +/// @nodoc +abstract mixin class _$SceneCopyWith<$Res> implements $SceneCopyWith<$Res> { + factory _$SceneCopyWith(_Scene value, $Res Function(_Scene) _then) = __$SceneCopyWithImpl; +@override @useResult +$Res call({ + SceneSetup setup, List commands +}); + + +@override $SceneSetupCopyWith<$Res> get setup; + +} +/// @nodoc +class __$SceneCopyWithImpl<$Res> + implements _$SceneCopyWith<$Res> { + __$SceneCopyWithImpl(this._self, this._then); + + final _Scene _self; + final $Res Function(_Scene) _then; + +/// Create a copy of Scene +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? setup = null,Object? commands = null,}) { + return _then(_Scene( +setup: null == setup ? _self.setup : setup // ignore: cast_nullable_to_non_nullable +as SceneSetup,commands: null == commands ? _self._commands : commands // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +/// Create a copy of Scene +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SceneSetupCopyWith<$Res> get setup { + + return $SceneSetupCopyWith<$Res>(_self.setup, (value) { + return _then(_self.copyWith(setup: value)); + }); +} +} + + +/// @nodoc +mixin _$SceneSetup { + + String get background; AudioClip get audio; List get characters; +/// Create a copy of SceneSetup +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SceneSetupCopyWith get copyWith => _$SceneSetupCopyWithImpl(this as SceneSetup, _$identity); + + /// Serializes this SceneSetup to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SceneSetup&&(identical(other.background, background) || other.background == background)&&(identical(other.audio, audio) || other.audio == audio)&&const DeepCollectionEquality().equals(other.characters, characters)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,background,audio,const DeepCollectionEquality().hash(characters)); + +@override +String toString() { + return 'SceneSetup(background: $background, audio: $audio, characters: $characters)'; +} + + +} + +/// @nodoc +abstract mixin class $SceneSetupCopyWith<$Res> { + factory $SceneSetupCopyWith(SceneSetup value, $Res Function(SceneSetup) _then) = _$SceneSetupCopyWithImpl; +@useResult +$Res call({ + String background, AudioClip audio, List characters +}); + + +$AudioClipCopyWith<$Res> get audio; + +} +/// @nodoc +class _$SceneSetupCopyWithImpl<$Res> + implements $SceneSetupCopyWith<$Res> { + _$SceneSetupCopyWithImpl(this._self, this._then); + + final SceneSetup _self; + final $Res Function(SceneSetup) _then; + +/// Create a copy of SceneSetup +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? background = null,Object? audio = null,Object? characters = null,}) { + return _then(_self.copyWith( +background: null == background ? _self.background : background // ignore: cast_nullable_to_non_nullable +as String,audio: null == audio ? _self.audio : audio // ignore: cast_nullable_to_non_nullable +as AudioClip,characters: null == characters ? _self.characters : characters // ignore: cast_nullable_to_non_nullable +as List, + )); +} +/// Create a copy of SceneSetup +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AudioClipCopyWith<$Res> get audio { + + return $AudioClipCopyWith<$Res>(_self.audio, (value) { + return _then(_self.copyWith(audio: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [SceneSetup]. +extension SceneSetupPatterns on SceneSetup { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SceneSetup value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SceneSetup() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SceneSetup value) $default,){ +final _that = this; +switch (_that) { +case _SceneSetup(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SceneSetup value)? $default,){ +final _that = this; +switch (_that) { +case _SceneSetup() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String background, AudioClip audio, List characters)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SceneSetup() when $default != null: +return $default(_that.background,_that.audio,_that.characters);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String background, AudioClip audio, List characters) $default,) {final _that = this; +switch (_that) { +case _SceneSetup(): +return $default(_that.background,_that.audio,_that.characters);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String background, AudioClip audio, List characters)? $default,) {final _that = this; +switch (_that) { +case _SceneSetup() when $default != null: +return $default(_that.background,_that.audio,_that.characters);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SceneSetup implements SceneSetup { + const _SceneSetup({required this.background, required this.audio, required final List characters}): _characters = characters; + factory _SceneSetup.fromJson(Map json) => _$SceneSetupFromJson(json); + +@override final String background; +@override final AudioClip audio; + final List _characters; +@override List get characters { + if (_characters is EqualUnmodifiableListView) return _characters; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_characters); +} + + +/// Create a copy of SceneSetup +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SceneSetupCopyWith<_SceneSetup> get copyWith => __$SceneSetupCopyWithImpl<_SceneSetup>(this, _$identity); + +@override +Map toJson() { + return _$SceneSetupToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SceneSetup&&(identical(other.background, background) || other.background == background)&&(identical(other.audio, audio) || other.audio == audio)&&const DeepCollectionEquality().equals(other._characters, _characters)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,background,audio,const DeepCollectionEquality().hash(_characters)); + +@override +String toString() { + return 'SceneSetup(background: $background, audio: $audio, characters: $characters)'; +} + + +} + +/// @nodoc +abstract mixin class _$SceneSetupCopyWith<$Res> implements $SceneSetupCopyWith<$Res> { + factory _$SceneSetupCopyWith(_SceneSetup value, $Res Function(_SceneSetup) _then) = __$SceneSetupCopyWithImpl; +@override @useResult +$Res call({ + String background, AudioClip audio, List characters +}); + + +@override $AudioClipCopyWith<$Res> get audio; + +} +/// @nodoc +class __$SceneSetupCopyWithImpl<$Res> + implements _$SceneSetupCopyWith<$Res> { + __$SceneSetupCopyWithImpl(this._self, this._then); + + final _SceneSetup _self; + final $Res Function(_SceneSetup) _then; + +/// Create a copy of SceneSetup +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? background = null,Object? audio = null,Object? characters = null,}) { + return _then(_SceneSetup( +background: null == background ? _self.background : background // ignore: cast_nullable_to_non_nullable +as String,audio: null == audio ? _self.audio : audio // ignore: cast_nullable_to_non_nullable +as AudioClip,characters: null == characters ? _self._characters : characters // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +/// Create a copy of SceneSetup +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$AudioClipCopyWith<$Res> get audio { + + return $AudioClipCopyWith<$Res>(_self.audio, (value) { + return _then(_self.copyWith(audio: value)); + }); +} +} + + +/// @nodoc +mixin _$SceneCharacter { + + String get character; ScenePosition get position; num? get opacity; +/// Create a copy of SceneCharacter +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SceneCharacterCopyWith get copyWith => _$SceneCharacterCopyWithImpl(this as SceneCharacter, _$identity); + + /// Serializes this SceneCharacter to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SceneCharacter&&(identical(other.character, character) || other.character == character)&&(identical(other.position, position) || other.position == position)&&(identical(other.opacity, opacity) || other.opacity == opacity)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,character,position,opacity); + +@override +String toString() { + return 'SceneCharacter(character: $character, position: $position, opacity: $opacity)'; +} + + +} + +/// @nodoc +abstract mixin class $SceneCharacterCopyWith<$Res> { + factory $SceneCharacterCopyWith(SceneCharacter value, $Res Function(SceneCharacter) _then) = _$SceneCharacterCopyWithImpl; +@useResult +$Res call({ + String character, ScenePosition position, num? opacity +}); + + +$ScenePositionCopyWith<$Res> get position; + +} +/// @nodoc +class _$SceneCharacterCopyWithImpl<$Res> + implements $SceneCharacterCopyWith<$Res> { + _$SceneCharacterCopyWithImpl(this._self, this._then); + + final SceneCharacter _self; + final $Res Function(SceneCharacter) _then; + +/// Create a copy of SceneCharacter +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? character = null,Object? position = null,Object? opacity = freezed,}) { + return _then(_self.copyWith( +character: null == character ? _self.character : character // ignore: cast_nullable_to_non_nullable +as String,position: null == position ? _self.position : position // ignore: cast_nullable_to_non_nullable +as ScenePosition,opacity: freezed == opacity ? _self.opacity : opacity // ignore: cast_nullable_to_non_nullable +as num?, + )); +} +/// Create a copy of SceneCharacter +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ScenePositionCopyWith<$Res> get position { + + return $ScenePositionCopyWith<$Res>(_self.position, (value) { + return _then(_self.copyWith(position: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [SceneCharacter]. +extension SceneCharacterPatterns on SceneCharacter { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SceneCharacter value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SceneCharacter() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SceneCharacter value) $default,){ +final _that = this; +switch (_that) { +case _SceneCharacter(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SceneCharacter value)? $default,){ +final _that = this; +switch (_that) { +case _SceneCharacter() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String character, ScenePosition position, num? opacity)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SceneCharacter() when $default != null: +return $default(_that.character,_that.position,_that.opacity);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String character, ScenePosition position, num? opacity) $default,) {final _that = this; +switch (_that) { +case _SceneCharacter(): +return $default(_that.character,_that.position,_that.opacity);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String character, ScenePosition position, num? opacity)? $default,) {final _that = this; +switch (_that) { +case _SceneCharacter() when $default != null: +return $default(_that.character,_that.position,_that.opacity);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SceneCharacter implements SceneCharacter { + const _SceneCharacter({required this.character, required this.position, this.opacity}); + factory _SceneCharacter.fromJson(Map json) => _$SceneCharacterFromJson(json); + +@override final String character; +@override final ScenePosition position; +@override final num? opacity; + +/// Create a copy of SceneCharacter +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SceneCharacterCopyWith<_SceneCharacter> get copyWith => __$SceneCharacterCopyWithImpl<_SceneCharacter>(this, _$identity); + +@override +Map toJson() { + return _$SceneCharacterToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SceneCharacter&&(identical(other.character, character) || other.character == character)&&(identical(other.position, position) || other.position == position)&&(identical(other.opacity, opacity) || other.opacity == opacity)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,character,position,opacity); + +@override +String toString() { + return 'SceneCharacter(character: $character, position: $position, opacity: $opacity)'; +} + + +} + +/// @nodoc +abstract mixin class _$SceneCharacterCopyWith<$Res> implements $SceneCharacterCopyWith<$Res> { + factory _$SceneCharacterCopyWith(_SceneCharacter value, $Res Function(_SceneCharacter) _then) = __$SceneCharacterCopyWithImpl; +@override @useResult +$Res call({ + String character, ScenePosition position, num? opacity +}); + + +@override $ScenePositionCopyWith<$Res> get position; + +} +/// @nodoc +class __$SceneCharacterCopyWithImpl<$Res> + implements _$SceneCharacterCopyWith<$Res> { + __$SceneCharacterCopyWithImpl(this._self, this._then); + + final _SceneCharacter _self; + final $Res Function(_SceneCharacter) _then; + +/// Create a copy of SceneCharacter +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? character = null,Object? position = null,Object? opacity = freezed,}) { + return _then(_SceneCharacter( +character: null == character ? _self.character : character // ignore: cast_nullable_to_non_nullable +as String,position: null == position ? _self.position : position // ignore: cast_nullable_to_non_nullable +as ScenePosition,opacity: freezed == opacity ? _self.opacity : opacity // ignore: cast_nullable_to_non_nullable +as num?, + )); +} + +/// Create a copy of SceneCharacter +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ScenePositionCopyWith<$Res> get position { + + return $ScenePositionCopyWith<$Res>(_self.position, (value) { + return _then(_self.copyWith(position: value)); + }); +} +} + + +/// @nodoc +mixin _$ScenePosition { + + num get x; num get y; num get z; +/// Create a copy of ScenePosition +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ScenePositionCopyWith get copyWith => _$ScenePositionCopyWithImpl(this as ScenePosition, _$identity); + + /// Serializes this ScenePosition to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ScenePosition&&(identical(other.x, x) || other.x == x)&&(identical(other.y, y) || other.y == y)&&(identical(other.z, z) || other.z == z)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,x,y,z); + +@override +String toString() { + return 'ScenePosition(x: $x, y: $y, z: $z)'; +} + + +} + +/// @nodoc +abstract mixin class $ScenePositionCopyWith<$Res> { + factory $ScenePositionCopyWith(ScenePosition value, $Res Function(ScenePosition) _then) = _$ScenePositionCopyWithImpl; +@useResult +$Res call({ + num x, num y, num z +}); + + + + +} +/// @nodoc +class _$ScenePositionCopyWithImpl<$Res> + implements $ScenePositionCopyWith<$Res> { + _$ScenePositionCopyWithImpl(this._self, this._then); + + final ScenePosition _self; + final $Res Function(ScenePosition) _then; + +/// Create a copy of ScenePosition +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? x = null,Object? y = null,Object? z = null,}) { + return _then(_self.copyWith( +x: null == x ? _self.x : x // ignore: cast_nullable_to_non_nullable +as num,y: null == y ? _self.y : y // ignore: cast_nullable_to_non_nullable +as num,z: null == z ? _self.z : z // ignore: cast_nullable_to_non_nullable +as num, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ScenePosition]. +extension ScenePositionPatterns on ScenePosition { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ScenePosition value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ScenePosition() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ScenePosition value) $default,){ +final _that = this; +switch (_that) { +case _ScenePosition(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ScenePosition value)? $default,){ +final _that = this; +switch (_that) { +case _ScenePosition() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( num x, num y, num z)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ScenePosition() when $default != null: +return $default(_that.x,_that.y,_that.z);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( num x, num y, num z) $default,) {final _that = this; +switch (_that) { +case _ScenePosition(): +return $default(_that.x,_that.y,_that.z);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( num x, num y, num z)? $default,) {final _that = this; +switch (_that) { +case _ScenePosition() when $default != null: +return $default(_that.x,_that.y,_that.z);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ScenePosition implements ScenePosition { + const _ScenePosition({required this.x, required this.y, required this.z}); + factory _ScenePosition.fromJson(Map json) => _$ScenePositionFromJson(json); + +@override final num x; +@override final num y; +@override final num z; + +/// Create a copy of ScenePosition +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ScenePositionCopyWith<_ScenePosition> get copyWith => __$ScenePositionCopyWithImpl<_ScenePosition>(this, _$identity); + +@override +Map toJson() { + return _$ScenePositionToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ScenePosition&&(identical(other.x, x) || other.x == x)&&(identical(other.y, y) || other.y == y)&&(identical(other.z, z) || other.z == z)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,x,y,z); + +@override +String toString() { + return 'ScenePosition(x: $x, y: $y, z: $z)'; +} + + +} + +/// @nodoc +abstract mixin class _$ScenePositionCopyWith<$Res> implements $ScenePositionCopyWith<$Res> { + factory _$ScenePositionCopyWith(_ScenePosition value, $Res Function(_ScenePosition) _then) = __$ScenePositionCopyWithImpl; +@override @useResult +$Res call({ + num x, num y, num z +}); + + + + +} +/// @nodoc +class __$ScenePositionCopyWithImpl<$Res> + implements _$ScenePositionCopyWith<$Res> { + __$ScenePositionCopyWithImpl(this._self, this._then); + + final _ScenePosition _self; + final $Res Function(_ScenePosition) _then; + +/// Create a copy of ScenePosition +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? x = null,Object? y = null,Object? z = null,}) { + return _then(_ScenePosition( +x: null == x ? _self.x : x // ignore: cast_nullable_to_non_nullable +as num,y: null == y ? _self.y : y // ignore: cast_nullable_to_non_nullable +as num,z: null == z ? _self.z : z // ignore: cast_nullable_to_non_nullable +as num, + )); +} + + +} + + +/// @nodoc +mixin _$SceneCommand { + + String get character; num get startTime; num? get finishTime; String? get background; ScenePosition? get position; num? get opacity; SceneDialogue? get dialogue; +/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SceneCommandCopyWith get copyWith => _$SceneCommandCopyWithImpl(this as SceneCommand, _$identity); + + /// Serializes this SceneCommand to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SceneCommand&&(identical(other.character, character) || other.character == character)&&(identical(other.startTime, startTime) || other.startTime == startTime)&&(identical(other.finishTime, finishTime) || other.finishTime == finishTime)&&(identical(other.background, background) || other.background == background)&&(identical(other.position, position) || other.position == position)&&(identical(other.opacity, opacity) || other.opacity == opacity)&&(identical(other.dialogue, dialogue) || other.dialogue == dialogue)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,character,startTime,finishTime,background,position,opacity,dialogue); + +@override +String toString() { + return 'SceneCommand(character: $character, startTime: $startTime, finishTime: $finishTime, background: $background, position: $position, opacity: $opacity, dialogue: $dialogue)'; +} + + +} + +/// @nodoc +abstract mixin class $SceneCommandCopyWith<$Res> { + factory $SceneCommandCopyWith(SceneCommand value, $Res Function(SceneCommand) _then) = _$SceneCommandCopyWithImpl; +@useResult +$Res call({ + String character, num startTime, num? finishTime, String? background, ScenePosition? position, num? opacity, SceneDialogue? dialogue +}); + + +$ScenePositionCopyWith<$Res>? get position;$SceneDialogueCopyWith<$Res>? get dialogue; + +} +/// @nodoc +class _$SceneCommandCopyWithImpl<$Res> + implements $SceneCommandCopyWith<$Res> { + _$SceneCommandCopyWithImpl(this._self, this._then); + + final SceneCommand _self; + final $Res Function(SceneCommand) _then; + +/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? character = null,Object? startTime = null,Object? finishTime = freezed,Object? background = freezed,Object? position = freezed,Object? opacity = freezed,Object? dialogue = freezed,}) { + return _then(_self.copyWith( +character: null == character ? _self.character : character // ignore: cast_nullable_to_non_nullable +as String,startTime: null == startTime ? _self.startTime : startTime // ignore: cast_nullable_to_non_nullable +as num,finishTime: freezed == finishTime ? _self.finishTime : finishTime // ignore: cast_nullable_to_non_nullable +as num?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable +as String?,position: freezed == position ? _self.position : position // ignore: cast_nullable_to_non_nullable +as ScenePosition?,opacity: freezed == opacity ? _self.opacity : opacity // ignore: cast_nullable_to_non_nullable +as num?,dialogue: freezed == dialogue ? _self.dialogue : dialogue // ignore: cast_nullable_to_non_nullable +as SceneDialogue?, + )); +} +/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ScenePositionCopyWith<$Res>? get position { + if (_self.position == null) { + return null; + } + + return $ScenePositionCopyWith<$Res>(_self.position!, (value) { + return _then(_self.copyWith(position: value)); + }); +}/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SceneDialogueCopyWith<$Res>? get dialogue { + if (_self.dialogue == null) { + return null; + } + + return $SceneDialogueCopyWith<$Res>(_self.dialogue!, (value) { + return _then(_self.copyWith(dialogue: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [SceneCommand]. +extension SceneCommandPatterns on SceneCommand { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SceneCommand value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SceneCommand() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SceneCommand value) $default,){ +final _that = this; +switch (_that) { +case _SceneCommand(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SceneCommand value)? $default,){ +final _that = this; +switch (_that) { +case _SceneCommand() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String character, num startTime, num? finishTime, String? background, ScenePosition? position, num? opacity, SceneDialogue? dialogue)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SceneCommand() when $default != null: +return $default(_that.character,_that.startTime,_that.finishTime,_that.background,_that.position,_that.opacity,_that.dialogue);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String character, num startTime, num? finishTime, String? background, ScenePosition? position, num? opacity, SceneDialogue? dialogue) $default,) {final _that = this; +switch (_that) { +case _SceneCommand(): +return $default(_that.character,_that.startTime,_that.finishTime,_that.background,_that.position,_that.opacity,_that.dialogue);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String character, num startTime, num? finishTime, String? background, ScenePosition? position, num? opacity, SceneDialogue? dialogue)? $default,) {final _that = this; +switch (_that) { +case _SceneCommand() when $default != null: +return $default(_that.character,_that.startTime,_that.finishTime,_that.background,_that.position,_that.opacity,_that.dialogue);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SceneCommand implements SceneCommand { + const _SceneCommand({required this.character, required this.startTime, this.finishTime, this.background, this.position, this.opacity, this.dialogue}); + factory _SceneCommand.fromJson(Map json) => _$SceneCommandFromJson(json); + +@override final String character; +@override final num startTime; +@override final num? finishTime; +@override final String? background; +@override final ScenePosition? position; +@override final num? opacity; +@override final SceneDialogue? dialogue; + +/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SceneCommandCopyWith<_SceneCommand> get copyWith => __$SceneCommandCopyWithImpl<_SceneCommand>(this, _$identity); + +@override +Map toJson() { + return _$SceneCommandToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SceneCommand&&(identical(other.character, character) || other.character == character)&&(identical(other.startTime, startTime) || other.startTime == startTime)&&(identical(other.finishTime, finishTime) || other.finishTime == finishTime)&&(identical(other.background, background) || other.background == background)&&(identical(other.position, position) || other.position == position)&&(identical(other.opacity, opacity) || other.opacity == opacity)&&(identical(other.dialogue, dialogue) || other.dialogue == dialogue)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,character,startTime,finishTime,background,position,opacity,dialogue); + +@override +String toString() { + return 'SceneCommand(character: $character, startTime: $startTime, finishTime: $finishTime, background: $background, position: $position, opacity: $opacity, dialogue: $dialogue)'; +} + + +} + +/// @nodoc +abstract mixin class _$SceneCommandCopyWith<$Res> implements $SceneCommandCopyWith<$Res> { + factory _$SceneCommandCopyWith(_SceneCommand value, $Res Function(_SceneCommand) _then) = __$SceneCommandCopyWithImpl; +@override @useResult +$Res call({ + String character, num startTime, num? finishTime, String? background, ScenePosition? position, num? opacity, SceneDialogue? dialogue +}); + + +@override $ScenePositionCopyWith<$Res>? get position;@override $SceneDialogueCopyWith<$Res>? get dialogue; + +} +/// @nodoc +class __$SceneCommandCopyWithImpl<$Res> + implements _$SceneCommandCopyWith<$Res> { + __$SceneCommandCopyWithImpl(this._self, this._then); + + final _SceneCommand _self; + final $Res Function(_SceneCommand) _then; + +/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? character = null,Object? startTime = null,Object? finishTime = freezed,Object? background = freezed,Object? position = freezed,Object? opacity = freezed,Object? dialogue = freezed,}) { + return _then(_SceneCommand( +character: null == character ? _self.character : character // ignore: cast_nullable_to_non_nullable +as String,startTime: null == startTime ? _self.startTime : startTime // ignore: cast_nullable_to_non_nullable +as num,finishTime: freezed == finishTime ? _self.finishTime : finishTime // ignore: cast_nullable_to_non_nullable +as num?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable +as String?,position: freezed == position ? _self.position : position // ignore: cast_nullable_to_non_nullable +as ScenePosition?,opacity: freezed == opacity ? _self.opacity : opacity // ignore: cast_nullable_to_non_nullable +as num?,dialogue: freezed == dialogue ? _self.dialogue : dialogue // ignore: cast_nullable_to_non_nullable +as SceneDialogue?, + )); +} + +/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ScenePositionCopyWith<$Res>? get position { + if (_self.position == null) { + return null; + } + + return $ScenePositionCopyWith<$Res>(_self.position!, (value) { + return _then(_self.copyWith(position: value)); + }); +}/// Create a copy of SceneCommand +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$SceneDialogueCopyWith<$Res>? get dialogue { + if (_self.dialogue == null) { + return null; + } + + return $SceneDialogueCopyWith<$Res>(_self.dialogue!, (value) { + return _then(_self.copyWith(dialogue: value)); + }); +} +} + + +/// @nodoc +mixin _$SceneDialogue { + + String get text; String get align; +/// Create a copy of SceneDialogue +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SceneDialogueCopyWith get copyWith => _$SceneDialogueCopyWithImpl(this as SceneDialogue, _$identity); + + /// Serializes this SceneDialogue to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SceneDialogue&&(identical(other.text, text) || other.text == text)&&(identical(other.align, align) || other.align == align)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text,align); + +@override +String toString() { + return 'SceneDialogue(text: $text, align: $align)'; +} + + +} + +/// @nodoc +abstract mixin class $SceneDialogueCopyWith<$Res> { + factory $SceneDialogueCopyWith(SceneDialogue value, $Res Function(SceneDialogue) _then) = _$SceneDialogueCopyWithImpl; +@useResult +$Res call({ + String text, String align +}); + + + + +} +/// @nodoc +class _$SceneDialogueCopyWithImpl<$Res> + implements $SceneDialogueCopyWith<$Res> { + _$SceneDialogueCopyWithImpl(this._self, this._then); + + final SceneDialogue _self; + final $Res Function(SceneDialogue) _then; + +/// Create a copy of SceneDialogue +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? text = null,Object? align = null,}) { + return _then(_self.copyWith( +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String,align: null == align ? _self.align : align // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SceneDialogue]. +extension SceneDialoguePatterns on SceneDialogue { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SceneDialogue value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SceneDialogue() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SceneDialogue value) $default,){ +final _that = this; +switch (_that) { +case _SceneDialogue(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SceneDialogue value)? $default,){ +final _that = this; +switch (_that) { +case _SceneDialogue() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String text, String align)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SceneDialogue() when $default != null: +return $default(_that.text,_that.align);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String text, String align) $default,) {final _that = this; +switch (_that) { +case _SceneDialogue(): +return $default(_that.text,_that.align);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String text, String align)? $default,) {final _that = this; +switch (_that) { +case _SceneDialogue() when $default != null: +return $default(_that.text,_that.align);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SceneDialogue implements SceneDialogue { + const _SceneDialogue({required this.text, required this.align}); + factory _SceneDialogue.fromJson(Map json) => _$SceneDialogueFromJson(json); + +@override final String text; +@override final String align; + +/// Create a copy of SceneDialogue +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SceneDialogueCopyWith<_SceneDialogue> get copyWith => __$SceneDialogueCopyWithImpl<_SceneDialogue>(this, _$identity); + +@override +Map toJson() { + return _$SceneDialogueToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SceneDialogue&&(identical(other.text, text) || other.text == text)&&(identical(other.align, align) || other.align == align)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,text,align); + +@override +String toString() { + return 'SceneDialogue(text: $text, align: $align)'; +} + + +} + +/// @nodoc +abstract mixin class _$SceneDialogueCopyWith<$Res> implements $SceneDialogueCopyWith<$Res> { + factory _$SceneDialogueCopyWith(_SceneDialogue value, $Res Function(_SceneDialogue) _then) = __$SceneDialogueCopyWithImpl; +@override @useResult +$Res call({ + String text, String align +}); + + + + +} +/// @nodoc +class __$SceneDialogueCopyWithImpl<$Res> + implements _$SceneDialogueCopyWith<$Res> { + __$SceneDialogueCopyWithImpl(this._self, this._then); + + final _SceneDialogue _self; + final $Res Function(_SceneDialogue) _then; + +/// Create a copy of SceneDialogue +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? text = null,Object? align = null,}) { + return _then(_SceneDialogue( +text: null == text ? _self.text : text // ignore: cast_nullable_to_non_nullable +as String,align: null == align ? _self.align : align // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/learn/models/challenge_model.g.dart b/mobile-app-new/lib/learn/models/challenge_model.g.dart new file mode 100644 index 000000000..321076cc6 --- /dev/null +++ b/mobile-app-new/lib/learn/models/challenge_model.g.dart @@ -0,0 +1,308 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'challenge_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_Challenge _$ChallengeFromJson(Map json) => _Challenge( + id: json['id'] as String, + block: json['block'] as String, + superBlock: json['superBlock'] as String, + title: json['title'] as String, + dashedName: json['dashedName'] as String, + challengeType: (json['challengeType'] as num).toInt(), + helpCategory: json['helpCategory'] as String? ?? '', + description: json['description'] as String? ?? '', + instructions: json['instructions'] as String? ?? '', + explanation: json['explanation'] as String? ?? '', + transcript: json['transcript'] as String? ?? '', + videoId: json['videoId'] as String?, + questions: + (json['questions'] as List?) + ?.map((e) => Question.fromJson(e as Map)) + .toList() ?? + const [], + assignments: + (json['assignments'] as List?) + ?.map((e) => e as String) + .toList() ?? + const [], + quizzes: + (json['quizzes'] as List?) + ?.map((e) => Quiz.fromJson(e as Map)) + .toList() ?? + const [], + nodules: + (json['nodules'] as List?) + ?.map((e) => Nodule.fromJson(e as Map)) + .toList() ?? + const [], + fillInTheBlank: json['fillInTheBlank'] == null + ? null + : FillInTheBlank.fromJson(json['fillInTheBlank'] as Map), + scene: json['scene'] == null + ? null + : Scene.fromJson(json['scene'] as Map), +); + +Map _$ChallengeToJson(_Challenge instance) => + { + 'id': instance.id, + 'block': instance.block, + 'superBlock': instance.superBlock, + 'title': instance.title, + 'dashedName': instance.dashedName, + 'challengeType': instance.challengeType, + 'helpCategory': instance.helpCategory, + 'description': instance.description, + 'instructions': instance.instructions, + 'explanation': instance.explanation, + 'transcript': instance.transcript, + 'videoId': instance.videoId, + 'questions': instance.questions, + 'assignments': instance.assignments, + 'quizzes': instance.quizzes, + 'nodules': instance.nodules, + 'fillInTheBlank': instance.fillInTheBlank, + 'scene': instance.scene, + }; + +_Question _$QuestionFromJson(Map json) => _Question( + text: json['text'] as String, + answers: (json['answers'] as List) + .map((e) => Answer.fromJson(e as Map)) + .toList(), + solution: (json['solution'] as num).toInt(), +); + +Map _$QuestionToJson(_Question instance) => { + 'text': instance.text, + 'answers': instance.answers, + 'solution': instance.solution, +}; + +_Answer _$AnswerFromJson(Map json) => _Answer( + answer: json['answer'] as String, + feedback: json['feedback'] as String?, +); + +Map _$AnswerToJson(_Answer instance) => { + 'answer': instance.answer, + 'feedback': instance.feedback, +}; + +_Quiz _$QuizFromJson(Map json) => _Quiz( + questions: (json['questions'] as List) + .map((e) => QuizQuestion.fromJson(e as Map)) + .toList(), +); + +Map _$QuizToJson(_Quiz instance) => { + 'questions': instance.questions, +}; + +_QuizQuestion _$QuizQuestionFromJson(Map json) => + _QuizQuestion( + text: json['text'] as String, + answer: json['answer'] as String, + distractors: (json['distractors'] as List) + .map((e) => e as String) + .toList(), + audioData: json['audioData'] == null + ? null + : QuizAudioData.fromJson(json['audioData'] as Map), + ); + +Map _$QuizQuestionToJson(_QuizQuestion instance) => + { + 'text': instance.text, + 'answer': instance.answer, + 'distractors': instance.distractors, + 'audioData': instance.audioData, + }; + +_QuizAudioData _$QuizAudioDataFromJson(Map json) => + _QuizAudioData( + audio: AudioClip.fromJson(json['audio'] as Map), + transcript: + (json['transcript'] as List?) + ?.map((e) => TranscriptLine.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$QuizAudioDataToJson(_QuizAudioData instance) => + { + 'audio': instance.audio, + 'transcript': instance.transcript, + }; + +_TranscriptLine _$TranscriptLineFromJson(Map json) => + _TranscriptLine( + character: json['character'] as String, + text: json['text'] as String, + ); + +Map _$TranscriptLineToJson(_TranscriptLine instance) => + {'character': instance.character, 'text': instance.text}; + +_AudioClip _$AudioClipFromJson(Map json) => _AudioClip( + filename: json['filename'] as String, + startTime: json['startTime'] as num? ?? 0, + startTimestamp: json['startTimestamp'] as num?, + finishTimestamp: json['finishTimestamp'] as num?, +); + +Map _$AudioClipToJson(_AudioClip instance) => + { + 'filename': instance.filename, + 'startTime': instance.startTime, + 'startTimestamp': instance.startTimestamp, + 'finishTimestamp': instance.finishTimestamp, + }; + +_FillInTheBlank _$FillInTheBlankFromJson(Map json) => + _FillInTheBlank( + sentence: json['sentence'] as String, + blanks: (json['blanks'] as List) + .map((e) => Blank.fromJson(e as Map)) + .toList(), + ); + +Map _$FillInTheBlankToJson(_FillInTheBlank instance) => + {'sentence': instance.sentence, 'blanks': instance.blanks}; + +_Blank _$BlankFromJson(Map json) => _Blank( + answer: json['answer'] as String, + feedback: json['feedback'] as String? ?? '', +); + +Map _$BlankToJson(_Blank instance) => { + 'answer': instance.answer, + 'feedback': instance.feedback, +}; + +ParagraphNodule _$ParagraphNoduleFromJson(Map json) => + ParagraphNodule( + contents: _readNoduleContents(json, 'contents') as String, + $type: json['type'] as String?, + ); + +Map _$ParagraphNoduleToJson(ParagraphNodule instance) => + {'contents': instance.contents, 'type': instance.$type}; + +InteractiveEditorNodule _$InteractiveEditorNoduleFromJson( + Map json, +) => InteractiveEditorNodule( + files: (_readNoduleContents(json, 'files') as List) + .map((e) => NoduleFile.fromJson(e as Map)) + .toList(), + $type: json['type'] as String?, +); + +Map _$InteractiveEditorNoduleToJson( + InteractiveEditorNodule instance, +) => {'files': instance.files, 'type': instance.$type}; + +_NoduleFile _$NoduleFileFromJson(Map json) => _NoduleFile( + name: json['name'] as String, + ext: json['ext'] as String, + contents: json['contents'] as String, +); + +Map _$NoduleFileToJson(_NoduleFile instance) => + { + 'name': instance.name, + 'ext': instance.ext, + 'contents': instance.contents, + }; + +_Scene _$SceneFromJson(Map json) => _Scene( + setup: SceneSetup.fromJson(json['setup'] as Map), + commands: (json['commands'] as List) + .map((e) => SceneCommand.fromJson(e as Map)) + .toList(), +); + +Map _$SceneToJson(_Scene instance) => { + 'setup': instance.setup, + 'commands': instance.commands, +}; + +_SceneSetup _$SceneSetupFromJson(Map json) => _SceneSetup( + background: json['background'] as String, + audio: AudioClip.fromJson(json['audio'] as Map), + characters: (json['characters'] as List) + .map((e) => SceneCharacter.fromJson(e as Map)) + .toList(), +); + +Map _$SceneSetupToJson(_SceneSetup instance) => + { + 'background': instance.background, + 'audio': instance.audio, + 'characters': instance.characters, + }; + +_SceneCharacter _$SceneCharacterFromJson(Map json) => + _SceneCharacter( + character: json['character'] as String, + position: ScenePosition.fromJson( + json['position'] as Map, + ), + opacity: json['opacity'] as num?, + ); + +Map _$SceneCharacterToJson(_SceneCharacter instance) => + { + 'character': instance.character, + 'position': instance.position, + 'opacity': instance.opacity, + }; + +_ScenePosition _$ScenePositionFromJson(Map json) => + _ScenePosition( + x: json['x'] as num, + y: json['y'] as num, + z: json['z'] as num, + ); + +Map _$ScenePositionToJson(_ScenePosition instance) => + {'x': instance.x, 'y': instance.y, 'z': instance.z}; + +_SceneCommand _$SceneCommandFromJson(Map json) => + _SceneCommand( + character: json['character'] as String, + startTime: json['startTime'] as num, + finishTime: json['finishTime'] as num?, + background: json['background'] as String?, + position: json['position'] == null + ? null + : ScenePosition.fromJson(json['position'] as Map), + opacity: json['opacity'] as num?, + dialogue: json['dialogue'] == null + ? null + : SceneDialogue.fromJson(json['dialogue'] as Map), + ); + +Map _$SceneCommandToJson(_SceneCommand instance) => + { + 'character': instance.character, + 'startTime': instance.startTime, + 'finishTime': instance.finishTime, + 'background': instance.background, + 'position': instance.position, + 'opacity': instance.opacity, + 'dialogue': instance.dialogue, + }; + +_SceneDialogue _$SceneDialogueFromJson(Map json) => + _SceneDialogue( + text: json['text'] as String, + align: json['align'] as String, + ); + +Map _$SceneDialogueToJson(_SceneDialogue instance) => + {'text': instance.text, 'align': instance.align}; diff --git a/mobile-app-new/lib/learn/models/scene_assets_model.dart b/mobile-app-new/lib/learn/models/scene_assets_model.dart new file mode 100644 index 000000000..b299a7603 --- /dev/null +++ b/mobile-app-new/lib/learn/models/scene_assets_model.dart @@ -0,0 +1,31 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'scene_assets_model.freezed.dart'; +part 'scene_assets_model.g.dart'; + +@freezed +abstract class SceneAssets with _$SceneAssets { + const factory SceneAssets({ + required String backgrounds, + required Map characterAssets, + }) = _SceneAssets; + + factory SceneAssets.fromJson(Map json) => + _$SceneAssetsFromJson(json); +} + +@freezed +abstract class CharacterAssets with _$CharacterAssets { + const factory CharacterAssets({ + required String base, + required String brows, + required String eyesClosed, + required String eyesOpen, + String? glasses, + required String mouthClosed, + required String mouthOpen, + }) = _CharacterAssets; + + factory CharacterAssets.fromJson(Map json) => + _$CharacterAssetsFromJson(json); +} diff --git a/mobile-app-new/lib/learn/models/scene_assets_model.freezed.dart b/mobile-app-new/lib/learn/models/scene_assets_model.freezed.dart new file mode 100644 index 000000000..772743705 --- /dev/null +++ b/mobile-app-new/lib/learn/models/scene_assets_model.freezed.dart @@ -0,0 +1,567 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'scene_assets_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$SceneAssets { + + String get backgrounds; Map get characterAssets; +/// Create a copy of SceneAssets +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SceneAssetsCopyWith get copyWith => _$SceneAssetsCopyWithImpl(this as SceneAssets, _$identity); + + /// Serializes this SceneAssets to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SceneAssets&&(identical(other.backgrounds, backgrounds) || other.backgrounds == backgrounds)&&const DeepCollectionEquality().equals(other.characterAssets, characterAssets)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,backgrounds,const DeepCollectionEquality().hash(characterAssets)); + +@override +String toString() { + return 'SceneAssets(backgrounds: $backgrounds, characterAssets: $characterAssets)'; +} + + +} + +/// @nodoc +abstract mixin class $SceneAssetsCopyWith<$Res> { + factory $SceneAssetsCopyWith(SceneAssets value, $Res Function(SceneAssets) _then) = _$SceneAssetsCopyWithImpl; +@useResult +$Res call({ + String backgrounds, Map characterAssets +}); + + + + +} +/// @nodoc +class _$SceneAssetsCopyWithImpl<$Res> + implements $SceneAssetsCopyWith<$Res> { + _$SceneAssetsCopyWithImpl(this._self, this._then); + + final SceneAssets _self; + final $Res Function(SceneAssets) _then; + +/// Create a copy of SceneAssets +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? backgrounds = null,Object? characterAssets = null,}) { + return _then(_self.copyWith( +backgrounds: null == backgrounds ? _self.backgrounds : backgrounds // ignore: cast_nullable_to_non_nullable +as String,characterAssets: null == characterAssets ? _self.characterAssets : characterAssets // ignore: cast_nullable_to_non_nullable +as Map, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SceneAssets]. +extension SceneAssetsPatterns on SceneAssets { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SceneAssets value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SceneAssets() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SceneAssets value) $default,){ +final _that = this; +switch (_that) { +case _SceneAssets(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SceneAssets value)? $default,){ +final _that = this; +switch (_that) { +case _SceneAssets() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String backgrounds, Map characterAssets)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SceneAssets() when $default != null: +return $default(_that.backgrounds,_that.characterAssets);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String backgrounds, Map characterAssets) $default,) {final _that = this; +switch (_that) { +case _SceneAssets(): +return $default(_that.backgrounds,_that.characterAssets);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String backgrounds, Map characterAssets)? $default,) {final _that = this; +switch (_that) { +case _SceneAssets() when $default != null: +return $default(_that.backgrounds,_that.characterAssets);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SceneAssets implements SceneAssets { + const _SceneAssets({required this.backgrounds, required final Map characterAssets}): _characterAssets = characterAssets; + factory _SceneAssets.fromJson(Map json) => _$SceneAssetsFromJson(json); + +@override final String backgrounds; + final Map _characterAssets; +@override Map get characterAssets { + if (_characterAssets is EqualUnmodifiableMapView) return _characterAssets; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_characterAssets); +} + + +/// Create a copy of SceneAssets +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SceneAssetsCopyWith<_SceneAssets> get copyWith => __$SceneAssetsCopyWithImpl<_SceneAssets>(this, _$identity); + +@override +Map toJson() { + return _$SceneAssetsToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SceneAssets&&(identical(other.backgrounds, backgrounds) || other.backgrounds == backgrounds)&&const DeepCollectionEquality().equals(other._characterAssets, _characterAssets)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,backgrounds,const DeepCollectionEquality().hash(_characterAssets)); + +@override +String toString() { + return 'SceneAssets(backgrounds: $backgrounds, characterAssets: $characterAssets)'; +} + + +} + +/// @nodoc +abstract mixin class _$SceneAssetsCopyWith<$Res> implements $SceneAssetsCopyWith<$Res> { + factory _$SceneAssetsCopyWith(_SceneAssets value, $Res Function(_SceneAssets) _then) = __$SceneAssetsCopyWithImpl; +@override @useResult +$Res call({ + String backgrounds, Map characterAssets +}); + + + + +} +/// @nodoc +class __$SceneAssetsCopyWithImpl<$Res> + implements _$SceneAssetsCopyWith<$Res> { + __$SceneAssetsCopyWithImpl(this._self, this._then); + + final _SceneAssets _self; + final $Res Function(_SceneAssets) _then; + +/// Create a copy of SceneAssets +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? backgrounds = null,Object? characterAssets = null,}) { + return _then(_SceneAssets( +backgrounds: null == backgrounds ? _self.backgrounds : backgrounds // ignore: cast_nullable_to_non_nullable +as String,characterAssets: null == characterAssets ? _self._characterAssets : characterAssets // ignore: cast_nullable_to_non_nullable +as Map, + )); +} + + +} + + +/// @nodoc +mixin _$CharacterAssets { + + String get base; String get brows; String get eyesClosed; String get eyesOpen; String? get glasses; String get mouthClosed; String get mouthOpen; +/// Create a copy of CharacterAssets +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CharacterAssetsCopyWith get copyWith => _$CharacterAssetsCopyWithImpl(this as CharacterAssets, _$identity); + + /// Serializes this CharacterAssets to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CharacterAssets&&(identical(other.base, base) || other.base == base)&&(identical(other.brows, brows) || other.brows == brows)&&(identical(other.eyesClosed, eyesClosed) || other.eyesClosed == eyesClosed)&&(identical(other.eyesOpen, eyesOpen) || other.eyesOpen == eyesOpen)&&(identical(other.glasses, glasses) || other.glasses == glasses)&&(identical(other.mouthClosed, mouthClosed) || other.mouthClosed == mouthClosed)&&(identical(other.mouthOpen, mouthOpen) || other.mouthOpen == mouthOpen)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,base,brows,eyesClosed,eyesOpen,glasses,mouthClosed,mouthOpen); + +@override +String toString() { + return 'CharacterAssets(base: $base, brows: $brows, eyesClosed: $eyesClosed, eyesOpen: $eyesOpen, glasses: $glasses, mouthClosed: $mouthClosed, mouthOpen: $mouthOpen)'; +} + + +} + +/// @nodoc +abstract mixin class $CharacterAssetsCopyWith<$Res> { + factory $CharacterAssetsCopyWith(CharacterAssets value, $Res Function(CharacterAssets) _then) = _$CharacterAssetsCopyWithImpl; +@useResult +$Res call({ + String base, String brows, String eyesClosed, String eyesOpen, String? glasses, String mouthClosed, String mouthOpen +}); + + + + +} +/// @nodoc +class _$CharacterAssetsCopyWithImpl<$Res> + implements $CharacterAssetsCopyWith<$Res> { + _$CharacterAssetsCopyWithImpl(this._self, this._then); + + final CharacterAssets _self; + final $Res Function(CharacterAssets) _then; + +/// Create a copy of CharacterAssets +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? base = null,Object? brows = null,Object? eyesClosed = null,Object? eyesOpen = null,Object? glasses = freezed,Object? mouthClosed = null,Object? mouthOpen = null,}) { + return _then(_self.copyWith( +base: null == base ? _self.base : base // ignore: cast_nullable_to_non_nullable +as String,brows: null == brows ? _self.brows : brows // ignore: cast_nullable_to_non_nullable +as String,eyesClosed: null == eyesClosed ? _self.eyesClosed : eyesClosed // ignore: cast_nullable_to_non_nullable +as String,eyesOpen: null == eyesOpen ? _self.eyesOpen : eyesOpen // ignore: cast_nullable_to_non_nullable +as String,glasses: freezed == glasses ? _self.glasses : glasses // ignore: cast_nullable_to_non_nullable +as String?,mouthClosed: null == mouthClosed ? _self.mouthClosed : mouthClosed // ignore: cast_nullable_to_non_nullable +as String,mouthOpen: null == mouthOpen ? _self.mouthOpen : mouthOpen // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [CharacterAssets]. +extension CharacterAssetsPatterns on CharacterAssets { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CharacterAssets value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CharacterAssets() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CharacterAssets value) $default,){ +final _that = this; +switch (_that) { +case _CharacterAssets(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CharacterAssets value)? $default,){ +final _that = this; +switch (_that) { +case _CharacterAssets() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String base, String brows, String eyesClosed, String eyesOpen, String? glasses, String mouthClosed, String mouthOpen)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CharacterAssets() when $default != null: +return $default(_that.base,_that.brows,_that.eyesClosed,_that.eyesOpen,_that.glasses,_that.mouthClosed,_that.mouthOpen);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String base, String brows, String eyesClosed, String eyesOpen, String? glasses, String mouthClosed, String mouthOpen) $default,) {final _that = this; +switch (_that) { +case _CharacterAssets(): +return $default(_that.base,_that.brows,_that.eyesClosed,_that.eyesOpen,_that.glasses,_that.mouthClosed,_that.mouthOpen);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String base, String brows, String eyesClosed, String eyesOpen, String? glasses, String mouthClosed, String mouthOpen)? $default,) {final _that = this; +switch (_that) { +case _CharacterAssets() when $default != null: +return $default(_that.base,_that.brows,_that.eyesClosed,_that.eyesOpen,_that.glasses,_that.mouthClosed,_that.mouthOpen);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _CharacterAssets implements CharacterAssets { + const _CharacterAssets({required this.base, required this.brows, required this.eyesClosed, required this.eyesOpen, this.glasses, required this.mouthClosed, required this.mouthOpen}); + factory _CharacterAssets.fromJson(Map json) => _$CharacterAssetsFromJson(json); + +@override final String base; +@override final String brows; +@override final String eyesClosed; +@override final String eyesOpen; +@override final String? glasses; +@override final String mouthClosed; +@override final String mouthOpen; + +/// Create a copy of CharacterAssets +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CharacterAssetsCopyWith<_CharacterAssets> get copyWith => __$CharacterAssetsCopyWithImpl<_CharacterAssets>(this, _$identity); + +@override +Map toJson() { + return _$CharacterAssetsToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CharacterAssets&&(identical(other.base, base) || other.base == base)&&(identical(other.brows, brows) || other.brows == brows)&&(identical(other.eyesClosed, eyesClosed) || other.eyesClosed == eyesClosed)&&(identical(other.eyesOpen, eyesOpen) || other.eyesOpen == eyesOpen)&&(identical(other.glasses, glasses) || other.glasses == glasses)&&(identical(other.mouthClosed, mouthClosed) || other.mouthClosed == mouthClosed)&&(identical(other.mouthOpen, mouthOpen) || other.mouthOpen == mouthOpen)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,base,brows,eyesClosed,eyesOpen,glasses,mouthClosed,mouthOpen); + +@override +String toString() { + return 'CharacterAssets(base: $base, brows: $brows, eyesClosed: $eyesClosed, eyesOpen: $eyesOpen, glasses: $glasses, mouthClosed: $mouthClosed, mouthOpen: $mouthOpen)'; +} + + +} + +/// @nodoc +abstract mixin class _$CharacterAssetsCopyWith<$Res> implements $CharacterAssetsCopyWith<$Res> { + factory _$CharacterAssetsCopyWith(_CharacterAssets value, $Res Function(_CharacterAssets) _then) = __$CharacterAssetsCopyWithImpl; +@override @useResult +$Res call({ + String base, String brows, String eyesClosed, String eyesOpen, String? glasses, String mouthClosed, String mouthOpen +}); + + + + +} +/// @nodoc +class __$CharacterAssetsCopyWithImpl<$Res> + implements _$CharacterAssetsCopyWith<$Res> { + __$CharacterAssetsCopyWithImpl(this._self, this._then); + + final _CharacterAssets _self; + final $Res Function(_CharacterAssets) _then; + +/// Create a copy of CharacterAssets +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? base = null,Object? brows = null,Object? eyesClosed = null,Object? eyesOpen = null,Object? glasses = freezed,Object? mouthClosed = null,Object? mouthOpen = null,}) { + return _then(_CharacterAssets( +base: null == base ? _self.base : base // ignore: cast_nullable_to_non_nullable +as String,brows: null == brows ? _self.brows : brows // ignore: cast_nullable_to_non_nullable +as String,eyesClosed: null == eyesClosed ? _self.eyesClosed : eyesClosed // ignore: cast_nullable_to_non_nullable +as String,eyesOpen: null == eyesOpen ? _self.eyesOpen : eyesOpen // ignore: cast_nullable_to_non_nullable +as String,glasses: freezed == glasses ? _self.glasses : glasses // ignore: cast_nullable_to_non_nullable +as String?,mouthClosed: null == mouthClosed ? _self.mouthClosed : mouthClosed // ignore: cast_nullable_to_non_nullable +as String,mouthOpen: null == mouthOpen ? _self.mouthOpen : mouthOpen // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/learn/models/scene_assets_model.g.dart b/mobile-app-new/lib/learn/models/scene_assets_model.g.dart new file mode 100644 index 000000000..432fb9b5a --- /dev/null +++ b/mobile-app-new/lib/learn/models/scene_assets_model.g.dart @@ -0,0 +1,42 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'scene_assets_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_SceneAssets _$SceneAssetsFromJson(Map json) => _SceneAssets( + backgrounds: json['backgrounds'] as String, + characterAssets: (json['characterAssets'] as Map).map( + (k, e) => MapEntry(k, CharacterAssets.fromJson(e as Map)), + ), +); + +Map _$SceneAssetsToJson(_SceneAssets instance) => + { + 'backgrounds': instance.backgrounds, + 'characterAssets': instance.characterAssets, + }; + +_CharacterAssets _$CharacterAssetsFromJson(Map json) => + _CharacterAssets( + base: json['base'] as String, + brows: json['brows'] as String, + eyesClosed: json['eyesClosed'] as String, + eyesOpen: json['eyesOpen'] as String, + glasses: json['glasses'] as String?, + mouthClosed: json['mouthClosed'] as String, + mouthOpen: json['mouthOpen'] as String, + ); + +Map _$CharacterAssetsToJson(_CharacterAssets instance) => + { + 'base': instance.base, + 'brows': instance.brows, + 'eyesClosed': instance.eyesClosed, + 'eyesOpen': instance.eyesOpen, + 'glasses': instance.glasses, + 'mouthClosed': instance.mouthClosed, + 'mouthOpen': instance.mouthOpen, + }; diff --git a/mobile-app-new/lib/learn/repositories/curriculum_repository.dart b/mobile-app-new/lib/learn/repositories/curriculum_repository.dart index da293a706..f8359fc66 100644 --- a/mobile-app-new/lib/learn/repositories/curriculum_repository.dart +++ b/mobile-app-new/lib/learn/repositories/curriculum_repository.dart @@ -1,30 +1,26 @@ import 'package:dio/dio.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:mobile_app_new/auth/repositories/session_repository.dart'; +import 'package:mobile_app_new/learn/constants/curriculum.dart'; import 'package:mobile_app_new/services/dio_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'curriculum_repository.g.dart'; -const _prodUrl = 'https://www.freecodecamp.org/curriculum-data/v2'; -const _devUrl = 'https://www.freecodecamp.dev/curriculum-data/v2'; - @riverpod LearnCurriculumRepository learnCurriculumRepository(Ref ref) => - LearnCurriculumRepository(ref.watch(dioProvider)); + LearnCurriculumRepository( + ref.watch(dioProvider), + ref.watch(authSessionRepositoryProvider), + ); class LearnCurriculumRepository { - LearnCurriculumRepository(this._dio); + LearnCurriculumRepository(this._dio, this._session); final Dio _dio; - - String get _baseUrl => - dotenv.getBool('DEVELOPMENT_MODE', fallback: false) ? _devUrl : _prodUrl; + final AuthSessionRepository _session; Future>>> getSuperBlockStages() async { - final response = await _dio.get>( - '$_baseUrl/available-superblocks.json', - ); - final stages = response.data?['superblocks']; + final stages = (await _get('available-superblocks.json'))['superblocks']; return { if (stages is Map) @@ -38,14 +34,43 @@ class LearnCurriculumRepository { } Future> getSuperBlock(String dashedName) async { - final response = await _dio.get>( - '$_baseUrl/$dashedName.json', - ); - final superBlock = response.data?.values.firstOrNull; + final superBlock = (await _get('$dashedName.json')).values.firstOrNull; if (superBlock is! Map) { throw FormatException('No superblock in $dashedName.json'); } return Map.from(superBlock); } + + Future> getChallenge( + String superBlock, + String block, + String challengeId, + ) => _get('challenges/$superBlock/$block/$challengeId.json'); + + Future> getSubmitTypes() => _get('submit-types.json'); + + Future> getSceneAssets() => _get('scene-assets.json'); + + Future postModernChallengeCompleted(Map body) => + _post('/modern-challenge-completed', body); + + Future postProjectCompleted(Map body) => + _post('/project-completed', body); + + Future> _get(String path) async { + final response = await _dio.get>( + '$curriculumDataUrl/$path', + ); + return response.data!; + } + + Future _post(String path, Map body) => + _session.authenticated( + (options) => _dio.post( + '${_session.apiUrl}$path', + data: body, + options: options, + ), + ); } diff --git a/mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart b/mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart index fe9f4eec6..1e3f209c8 100644 --- a/mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart +++ b/mobile-app-new/lib/learn/repositories/curriculum_repository.g.dart @@ -55,4 +55,4 @@ final class LearnCurriculumRepositoryProvider } String _$learnCurriculumRepositoryHash() => - r'c8bbb55b468c9158e97204e26db19a27b238d89d'; + r'598580d4fbebb08e6b30e961d01b96b3c12f10db'; diff --git a/mobile-app-new/lib/learn/services/curriculum_service.dart b/mobile-app-new/lib/learn/services/curriculum_service.dart index 3ce57689b..97e6627ca 100644 --- a/mobile-app-new/lib/learn/services/curriculum_service.dart +++ b/mobile-app-new/lib/learn/services/curriculum_service.dart @@ -1,6 +1,8 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:mobile_app_new/learn/constants/curriculum.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/models/scene_assets_model.dart'; import 'package:mobile_app_new/learn/repositories/curriculum_repository.dart'; import 'package:mobile_app_new/services/remote_config_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -24,7 +26,7 @@ class LearnCurriculumService { Future> getStages(RemoteConfig remoteConfig) async { final stages = await _repo.getSuperBlockStages(); - final showAll = dotenv.getBool('SHOWALLSB', fallback: false); + final showAll = dotenv.getBool('SHOW_ALL_SB', fallback: false); return [ for (final MapEntry(key: stage, value: title) in stageTitles.entries) @@ -163,4 +165,27 @@ class LearnCurriculumService { block['meta'] as Map; int _order(Map block) => _meta(block)['order'] as int? ?? 0; + + Future getChallenge( + String superBlock, + String block, + String challengeId, + ) async => Challenge.fromJson( + await _repo.getChallenge(superBlock, block, challengeId), + ); + + Future getSceneAssets() async => + SceneAssets.fromJson(await _repo.getSceneAssets()); + + Future submit(Challenge challenge, {String? solution}) async { + final submitTypes = await _repo.getSubmitTypes(); + final body = {'id': challenge.id, 'challengeType': challenge.challengeType}; + + switch (submitTypes['${challenge.challengeType}']) { + case 'tests': + await _repo.postModernChallengeCompleted(body); + case 'project.backEnd': + await _repo.postProjectCompleted({...body, 'solution': solution}); + } + } } diff --git a/mobile-app-new/lib/learn/ui/challenge/challenge_view.dart b/mobile-app-new/lib/learn/ui/challenge/challenge_view.dart new file mode 100644 index 000000000..3e9887d17 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/challenge_view.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_placeholder_view.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_viewmodel.dart'; +import 'package:mobile_app_new/learn/ui/challenge/english_view.dart'; +import 'package:mobile_app_new/learn/ui/challenge/lesson_view.dart'; +import 'package:mobile_app_new/learn/ui/challenge/python_project_view.dart'; +import 'package:mobile_app_new/learn/ui/challenge/quiz_view.dart'; +import 'package:mobile_app_new/widgets/error_retry.dart'; + +class ChallengeView extends ConsumerWidget { + const ChallengeView({ + super.key, + required this.superBlock, + required this.block, + required this.challengeId, + }); + + final String superBlock; + final String block; + final String challengeId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final provider = challengeScreenProvider(superBlock, block, challengeId); + + return switch (ref.watch(provider)) { + AsyncData(value: (:final challenge, :final block)) => + switch (challenge.challengeType) { + // quiz + 8 => QuizView(challenge: challenge, block: block), + // pythonProject + 10 => PythonProjectView(challenge: challenge, block: block), + // video + 11 => LessonView(challenge: challenge, block: block), + // theOdinProject + 15 => LessonView(challenge: challenge, block: block), + // multipleChoice + 19 => LessonView(challenge: challenge, block: block), + // generic + 24 => LessonView(challenge: challenge, block: block), + // review + 31 => LessonView(challenge: challenge, block: block), + // dialogue + 21 => EnglishView(challenge: challenge, block: block), + // fillInTheBlank + 22 => EnglishView(challenge: challenge, block: block), + // NOTE: The editor challenge types arrive in Learn iteration 4. + // html + 0 => _editorPlaceholder(challenge), + // js + 1 => _editorPlaceholder(challenge), + // multifileCertProject + 14 => _editorPlaceholder(challenge), + // python + 20 => _editorPlaceholder(challenge), + // multifilePythonCertProject + 23 => _editorPlaceholder(challenge), + // lab + 25 => _editorPlaceholder(challenge), + // jsLab + 26 => _editorPlaceholder(challenge), + // pyLab + 27 => _editorPlaceholder(challenge), + // dailyChallengeJs + 28 => _editorPlaceholder(challenge), + // dailyChallengePy + 29 => _editorPlaceholder(challenge), + final type => _Message( + child: Text( + 'This challenge type ($type) is not available in the app yet.', + textAlign: TextAlign.center, + ), + ), + }, + AsyncError() => _Message( + child: ErrorRetry( + message: 'Something went wrong!', + onRetry: () => ref.invalidate(provider), + ), + ), + _ => const _Message(child: CircularProgressIndicator()), + }; + } +} + +Widget _editorPlaceholder(Challenge challenge) => ChallengePlaceholderView( + details: '${challenge.title} (type ${challenge.challengeType})', +); + +class _Message extends StatelessWidget { + const _Message({required this.child}); + + final Widget child; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: Center( + child: Padding(padding: const EdgeInsets.all(16), child: child), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.dart b/mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.dart new file mode 100644 index 000000000..45d026ee1 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.dart @@ -0,0 +1,81 @@ +import 'package:collection/collection.dart'; +import 'package:mobile_app_new/learn/controllers/curriculum_controller.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/services/curriculum_service.dart'; +import 'package:mobile_app_new/services/auth_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'challenge_viewmodel.g.dart'; + +typedef ChallengeScreenData = ({Challenge challenge, Block block}); + +@riverpod +Future challengeScreen( + Ref ref, + String superBlock, + String block, + String challengeId, +) async { + final (challenge, curriculum) = await ( + ref + .watch(learnCurriculumServiceProvider) + .getChallenge(superBlock, block, challengeId), + ref.watch(superBlockProvider(superBlock).future), + ).wait; + + final blocks = switch (curriculum) { + BlockSuperBlock(:final blocks) => blocks, + ChapterSuperBlock(:final chapters) => [ + for (final chapter in chapters) + for (final module in chapter.modules) ...module.blocks, + ], + }; + final found = blocks.firstWhereOrNull((b) => b.dashedName == block); + if (found == null) throw StateError('Block $block not in $superBlock'); + + return (challenge: challenge, block: found); +} + +@riverpod +class ChallengeSubmitNotifier extends _$ChallengeSubmitNotifier { + @override + AsyncValue build(String challengeId) => const AsyncData(null); + + Future submit(Challenge challenge, {String? solution}) async { + if (!ref.read(authProvider).isLoggedIn) return true; + + state = const AsyncLoading(); + try { + await ref + .read(learnCurriculumServiceProvider) + .submit(challenge, solution: solution); + await ref.read(authProvider.notifier).refresh(); + if (ref.mounted) state = const AsyncData(null); + return true; + } catch (e, stack) { + if (ref.mounted) state = AsyncError(e, stack); + return false; + } + } +} + +// NOTE: English tasks are titled "Task N"; dialogue headers and single-challenge blocks get no title. +String challengeTitle(Challenge challenge, Block block) { + final total = block.challenges.length; + if (total == 1 || + challenge.title.contains('Dialogue') || + challenge.title.contains('Step')) { + return ''; + } + + if (challenge.title.contains('Task')) { + final dialogues = block.challenges + .where((c) => c.title.contains('Dialogue')) + .length; + return '${challenge.title} of ${total - dialogues}'; + } + + final step = block.challenges.indexWhere((c) => c.id == challenge.id) + 1; + return 'Step $step of $total'; +} diff --git a/mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.g.dart b/mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.g.dart new file mode 100644 index 000000000..204fae7e2 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/challenge_viewmodel.g.dart @@ -0,0 +1,196 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'challenge_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(challengeScreen) +final challengeScreenProvider = ChallengeScreenFamily._(); + +final class ChallengeScreenProvider + extends + $FunctionalProvider< + AsyncValue, + ChallengeScreenData, + FutureOr + > + with + $FutureModifier, + $FutureProvider { + ChallengeScreenProvider._({ + required ChallengeScreenFamily super.from, + required (String, String, String) super.argument, + }) : super( + retry: null, + name: r'challengeScreenProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$challengeScreenHash(); + + @override + String toString() { + return r'challengeScreenProvider' + '' + '$argument'; + } + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + final argument = this.argument as (String, String, String); + return challengeScreen(ref, argument.$1, argument.$2, argument.$3); + } + + @override + bool operator ==(Object other) { + return other is ChallengeScreenProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$challengeScreenHash() => r'aaa487fd4d644debd080b833ffb921d83db9fd1a'; + +final class ChallengeScreenFamily extends $Family + with + $FunctionalFamilyOverride< + FutureOr, + (String, String, String) + > { + ChallengeScreenFamily._() + : super( + retry: null, + name: r'challengeScreenProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ChallengeScreenProvider call( + String superBlock, + String block, + String challengeId, + ) => ChallengeScreenProvider._( + argument: (superBlock, block, challengeId), + from: this, + ); + + @override + String toString() => r'challengeScreenProvider'; +} + +@ProviderFor(ChallengeSubmitNotifier) +final challengeSubmitProvider = ChallengeSubmitNotifierFamily._(); + +final class ChallengeSubmitNotifierProvider + extends $NotifierProvider> { + ChallengeSubmitNotifierProvider._({ + required ChallengeSubmitNotifierFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'challengeSubmitProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$challengeSubmitNotifierHash(); + + @override + String toString() { + return r'challengeSubmitProvider' + '' + '($argument)'; + } + + @$internal + @override + ChallengeSubmitNotifier create() => ChallengeSubmitNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(AsyncValue value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider>(value), + ); + } + + @override + bool operator ==(Object other) { + return other is ChallengeSubmitNotifierProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$challengeSubmitNotifierHash() => + r'53619bbc01dfbda8656f0d5b6e6343c6cb8e5b71'; + +final class ChallengeSubmitNotifierFamily extends $Family + with + $ClassFamilyOverride< + ChallengeSubmitNotifier, + AsyncValue, + AsyncValue, + AsyncValue, + String + > { + ChallengeSubmitNotifierFamily._() + : super( + retry: null, + name: r'challengeSubmitProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + ChallengeSubmitNotifierProvider call(String challengeId) => + ChallengeSubmitNotifierProvider._(argument: challengeId, from: this); + + @override + String toString() => r'challengeSubmitProvider'; +} + +abstract class _$ChallengeSubmitNotifier extends $Notifier> { + late final _$args = ref.$arg as String; + String get challengeId => _$args; + + AsyncValue build(String challengeId); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref, AsyncValue>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, AsyncValue>, + AsyncValue, + Object?, + Object? + >; + return element.handleCreate(ref, () => build(_$args)); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/english_view.dart b/mobile-app-new/lib/learn/ui/challenge/english_view.dart new file mode 100644 index 000000000..19e97501f --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/english_view.dart @@ -0,0 +1,186 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:html/parser.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/html_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/parsed_html.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/complete_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_viewmodel.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/help_dialog.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_view.dart'; + +const _wordStyle = TextStyle(fontSize: 20, letterSpacing: 0); + +String _plain(String html) => parseFragment(html).text ?? ''; + +class EnglishView extends StatefulWidget { + const EnglishView({super.key, required this.challenge, required this.block}); + + final Challenge challenge; + final Block block; + + @override + State createState() => _EnglishViewState(); +} + +class _EnglishViewState extends State { + late final _blanks = widget.challenge.fillInTheBlank?.blanks ?? const []; + late final _inputs = [for (final _ in _blanks) TextEditingController()]; + List? _results; + + bool get _allCorrect => _results?.every((r) => r) ?? false; + + @override + void dispose() { + for (final input in _inputs) { + input.dispose(); + } + super.dispose(); + } + + void _check() => setState(() { + _results = [ + for (final (i, blank) in _blanks.indexed) + _inputs[i].text.trim().toLowerCase() == blank.answer.toLowerCase(), + ]; + }); + + @override + Widget build(BuildContext context) { + final challenge = widget.challenge; + final gray = { + '*:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6)': Style( + color: FccColors.gray05, + ), + }; + final results = _results; + final firstWrong = results?.indexOf(false) ?? -1; + final feedback = firstWrong == -1 ? '' : _blanks[firstWrong].feedback; + + return Scaffold( + backgroundColor: FccColors.gray90, + appBar: AppBar( + backgroundColor: FccColors.gray90, + title: Text(challengeTitle(challenge, widget.block)), + ), + body: SafeArea( + child: ListView( + children: [ + ChallengeCard( + title: challenge.title, + child: Column( + children: [ + ParsedHtml(challenge.description, customStyles: gray), + ParsedHtml(challenge.instructions, customStyles: gray), + ], + ), + ), + if (challenge.scene case final scene?) + ChallengeCard( + title: 'Scene', + child: SceneView(scene: scene, title: challenge.title), + ), + if (challenge.fillInTheBlank case final fill?) + ChallengeCard( + title: 'Fill in the Blank', + child: Padding( + padding: const EdgeInsets.all(10), + child: Column(children: _sentences(fill.sentence)), + ), + ), + if (feedback.isNotEmpty) + HtmlCard(title: 'Feedback', html: feedback), + if (challenge.explanation.isNotEmpty) + HtmlCard( + title: 'Explanation', + html: challenge.explanation, + collapsible: true, + ), + if (_blanks.isEmpty || _allCorrect) + CompleteButton( + challenge: challenge, + block: widget.block, + label: 'Go to Next Challenge', + ) + else + ChallengeButton(label: 'Check Answers', onPressed: _check), + ChallengeButton( + label: 'Ask for Help', + onPressed: () => showHelpDialog(context, challenge, widget.block), + ), + ], + ), + ), + ); + } + + // NOTE: A blank can be glued to the words around it ("BLANK," or "(BLANK)"), so each word splits on it. + List _sentences(String sentence) { + var blank = 0; + + return [ + for (final line in sentence.split('\n')) + Padding( + padding: const EdgeInsets.only(top: 16), + child: Align( + alignment: Alignment.topLeft, + child: Wrap( + spacing: 3, + runSpacing: 3, + children: [ + for (final word in line.split(' ')) + if (word.contains('BLANK')) ...[ + for (final (i, part) in word.split('BLANK').indexed) ...[ + if (i > 0) _input(blank++), + if (part.isNotEmpty) + Text(_plain(part), style: _wordStyle), + ], + ] else + Text(_plain(word), style: _wordStyle), + ], + ), + ), + ), + ]; + } + + Widget _input(int index) { + final result = _results?[index]; + final border = OutlineInputBorder( + borderSide: switch (result) { + null => const BorderSide(color: FccColors.blue50), + true => const BorderSide(width: 2, color: Colors.green), + false => const BorderSide(width: 2, color: Colors.red), + }, + ); + final painter = TextPainter( + text: TextSpan(text: _blanks[index].answer, style: _wordStyle), + textDirection: TextDirection.ltr, + )..layout(); + final answerWidth = painter.width; + painter.dispose(); + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 5), + width: answerWidth + 20, + child: TextField( + controller: _inputs[index], + cursorHeight: 19, + autocorrect: false, + smartQuotesType: SmartQuotesType.disabled, + spellCheckConfiguration: const SpellCheckConfiguration.disabled(), + style: const TextStyle(fontSize: 19, letterSpacing: 0), + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric(horizontal: 8), + enabledBorder: border, + focusedBorder: border, + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/lesson_view.dart b/mobile-app-new/lib/learn/ui/challenge/lesson_view.dart new file mode 100644 index 000000000..0c9551580 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/lesson_view.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/html_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/complete_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/assignment_tile.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/example_editor.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_video.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_viewmodel.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/help_dialog.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/questions.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_view.dart'; + +class LessonView extends StatefulWidget { + const LessonView({super.key, required this.challenge, required this.block}); + + final Challenge challenge; + final Block block; + + @override + State createState() => _LessonViewState(); +} + +class _LessonViewState extends State { + late final _questions = widget.challenge.questions.map(questionItem).toList(); + late final _selected = List.filled(_questions.length, null); + late final _assignments = List.filled( + widget.challenge.assignments.length, + false, + ); + bool _validated = false; + + bool get _passed => [ + for (final (i, q) in _questions.indexed) _selected[i] == q.solution, + ].every((correct) => correct); + + bool get _canCheck => + _assignments.every((done) => done) && !_selected.contains(null); + + @override + Widget build(BuildContext context) { + final challenge = widget.challenge; + final hasQuestions = _questions.isNotEmpty; + final showHelp = ![24, 31].contains(challenge.challengeType); + + return Scaffold( + appBar: AppBar(title: Text(challengeTitle(challenge, widget.block))), + body: SafeArea( + child: ListView( + padding: const EdgeInsets.only(bottom: 50), + children: [ + if (challenge.description.isNotEmpty) + HtmlCard(title: 'Description', html: challenge.description), + if (challenge.nodules.isNotEmpty) + ExampleEditor(nodules: challenge.nodules), + if (challenge.videoId case final videoId?) + ChallengeCard( + title: 'Watch the Video', + child: ChallengeVideo(videoId: videoId), + ), + if (challenge.transcript.isNotEmpty) + HtmlCard( + title: 'Transcript', + html: challenge.transcript, + collapsible: challenge.videoId != null, + ), + if (challenge.scene case final scene?) + ChallengeCard( + title: 'Scene', + child: SceneView(scene: scene, title: challenge.title), + ), + if (challenge.instructions.isNotEmpty) + HtmlCard(title: 'Instructions', html: challenge.instructions), + if (challenge.assignments.isNotEmpty) + ChallengeCard( + title: 'Assignments', + child: Column( + children: [ + for (final (i, assignment) in challenge.assignments.indexed) + AssignmentTile( + label: assignment, + value: _assignments[i], + onToggle: () => + setState(() => _assignments[i] = !_assignments[i]), + ), + ], + ), + ), + Questions( + questions: _questions, + selected: _selected, + validated: _validated, + onChanged: (question, answer) => setState(() { + _selected[question] = answer; + _validated = false; + }), + ), + if (challenge.explanation.isNotEmpty) + HtmlCard( + title: 'Explanation', + html: challenge.explanation, + collapsible: true, + ), + if (_validated && !_passed && _questions.length > 1) + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Text( + 'Some answers are incorrect.', + style: TextStyle(fontSize: 18), + ), + ), + if (!hasQuestions || (_validated && _passed)) + CompleteButton( + challenge: challenge, + block: widget.block, + label: hasQuestions ? 'Next Challenge' : 'Next', + enabled: _canCheck, + ) + else + ChallengeButton( + label: _validated ? 'Try Again' : 'Check your Answer', + onPressed: _canCheck + ? () => setState(() => _validated = true) + : null, + ), + if (showHelp) + ChallengeButton( + label: 'Ask for Help', + onPressed: () => + showHelpDialog(context, challenge, widget.block), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/python_project_view.dart b/mobile-app-new/lib/learn/ui/challenge/python_project_view.dart new file mode 100644 index 000000000..dfb4fee97 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/python_project_view.dart @@ -0,0 +1,132 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/complete_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/help_dialog.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/parsed_html.dart'; + +final _fccRegex = RegExp( + r'codepen\.io\/freecodecamp|freecodecamp\.rocks|github\.com\/freecodecamp|\.freecodecamp\.org', + caseSensitive: false, +); +final _localhostRegex = RegExp(r'localhost:'); +final _httpRegex = RegExp(r'http(?!s|([^s]+?localhost))'); + +String? _linkError(String link) { + if (!(Uri.tryParse(link)?.isAbsolute ?? false)) { + return 'Please enter a valid link.'; + } + if (_fccRegex.hasMatch(link)) return 'Remember to submit your own work.'; + if (_httpRegex.hasMatch(link)) { + return 'An unsecure (http) URL cannot be used.'; + } + if (_localhostRegex.hasMatch(link)) { + return 'Remember to submit a publicly visible app URL.'; + } + return null; +} + +class PythonProjectView extends StatefulWidget { + const PythonProjectView({ + super.key, + required this.challenge, + required this.block, + }); + + final Challenge challenge; + final Block block; + + @override + State createState() => _PythonProjectViewState(); +} + +class _PythonProjectViewState extends State { + final _link = TextEditingController(); + bool _checked = false; + String? _error; + + bool get _valid => _checked && _error == null; + + @override + void dispose() { + _link.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final challenge = widget.challenge; + final border = BorderSide( + color: _valid ? Colors.green : FccColors.gray00, + width: 2, + ); + + return Scaffold( + appBar: AppBar(title: Text(challenge.title)), + body: SafeArea( + bottom: false, + child: ListView( + padding: const EdgeInsets.fromLTRB(12, 12, 12, 48), + children: [ + ParsedHtml(challenge.description), + const Divider(color: FccColors.gray00), + ParsedHtml(challenge.instructions), + const Divider(color: FccColors.gray00), + const Padding( + padding: EdgeInsets.fromLTRB(12, 8, 12, 8), + child: Text( + 'Solution Link', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: TextField( + controller: _link, + keyboardType: TextInputType.url, + autocorrect: false, + onChanged: (_) => setState(() { + _checked = false; + _error = null; + }), + decoration: InputDecoration( + hintText: 'ex: https://replit.com/@camperbot/hello', + errorText: _error, + errorMaxLines: 5, + enabledBorder: OutlineInputBorder( + borderSide: border.copyWith(width: 1), + ), + focusedBorder: OutlineInputBorder(borderSide: border), + ), + ), + ), + const SizedBox(height: 8), + if (_valid) + CompleteButton( + challenge: challenge, + block: widget.block, + label: 'Submit', + solution: _link.text, + ) + else + ChallengeButton( + label: 'Check Link', + onPressed: _link.text.isEmpty + ? null + : () => setState(() { + _checked = true; + _error = _linkError(_link.text); + }), + ), + ChallengeButton( + label: 'Ask for Help', + onPressed: () => showHelpDialog(context, challenge, widget.block), + ), + ], + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/quiz_view.dart b/mobile-app-new/lib/learn/ui/challenge/quiz_view.dart new file mode 100644 index 000000000..efb7f26f2 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/quiz_view.dart @@ -0,0 +1,191 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/complete_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/parsed_html.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/questions.dart'; +import 'package:mobile_app_new/widgets/dialog/confirm_dialog.dart'; + +// NOTE: The quiz picks one of its question sets at random and shuffles each question's answers. +List _pickQuestions(List quizzes) { + if (quizzes.isEmpty) return const []; + + final random = Random(); + final quiz = quizzes[random.nextInt(quizzes.length)]; + + return [ + for (final q in quiz.questions) + () { + final answers = [ + for (final text in [...q.distractors, q.answer]) Answer(answer: text), + ]..shuffle(random); + + return ( + text: q.text, + answers: answers, + solution: answers.indexWhere((a) => a.answer == q.answer), + audioData: q.audioData, + ); + }(), + ]; +} + +class QuizView extends StatefulWidget { + const QuizView({super.key, required this.challenge, required this.block}); + + final Challenge challenge; + final Block block; + + @override + State createState() => _QuizViewState(); +} + +class _QuizViewState extends State { + static const _passRate = 0.9; + + late final _questions = _pickQuestions(widget.challenge.quizzes); + late List _selected = List.filled(_questions.length, null); + bool _validated = false; + String _message = ''; + + int get _correct => [ + for (final (i, q) in _questions.indexed) + if (_selected[i] == q.solution) q, + ].length; + + bool get _passed => + _validated && _correct >= (_questions.length * _passRate).ceil(); + + void _check() { + final unanswered = [ + for (final (i, answer) in _selected.indexed) + if (answer == null) i + 1, + ]; + + setState(() { + if (unanswered.isNotEmpty) { + _message = + 'The following questions are unanswered: ${unanswered.join(', ')}. You must answer all questions.'; + return; + } + + _validated = true; + final total = _questions.length; + _message = _passed + ? '✅ You have $_correct out of $total questions correct. You have passed.' + : '❌ You have $_correct out of $total questions correct. You did not pass.'; + }); + } + + void _reset() => setState(() { + _selected = List.filled(_questions.length, null); + _validated = false; + _message = ''; + }); + + @override + Widget build(BuildContext context) { + final challenge = widget.challenge; + final gray = { + '*:not(h1):not(h2):not(h3):not(h4):not(h5):not(h6)': Style( + color: FccColors.gray05, + ), + }; + + return PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, _) async { + if (didPop) return; + + final leave = await showConfirmDialog( + context, + title: 'Are you sure?', + description: + 'Do you want to leave this quiz? Your progress will be lost.', + confirmLabel: 'Leave', + destructive: true, + ); + if (leave && context.mounted) Navigator.of(context).pop(); + }, + child: Scaffold( + backgroundColor: FccColors.gray90, + appBar: AppBar(backgroundColor: FccColors.gray90), + body: SafeArea( + bottom: false, + child: ListView( + padding: const EdgeInsets.only(bottom: 16), + children: [ + ChallengeCard( + title: challenge.title, + child: Column( + children: [ + ParsedHtml(challenge.description, customStyles: gray), + ParsedHtml(challenge.instructions, customStyles: gray), + ], + ), + ), + Questions( + questions: _questions, + selected: _selected, + validated: _validated, + onChanged: (question, answer) => setState(() { + _selected[question] = answer; + _validated = false; + _message = ''; + }), + ), + ], + ), + ), + bottomNavigationBar: Container( + decoration: BoxDecoration( + color: FccColors.gray80, + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.3), + blurRadius: 4, + offset: const Offset(0, -2), + ), + ], + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (_message.isNotEmpty) + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 4), + child: Text( + _message, + style: const TextStyle(fontSize: 18), + ), + ), + if (_passed) + CompleteButton( + challenge: challenge, + block: widget.block, + label: 'Next Challenge', + ) + else + ChallengeButton( + label: _validated ? 'Try Again' : 'Check your Answer', + onPressed: _validated ? _reset : _check, + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/assignment_tile.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/assignment_tile.dart new file mode 100644 index 000000000..41240d0c1 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/assignment_tile.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; + +class AssignmentTile extends StatelessWidget { + const AssignmentTile({ + super.key, + required this.label, + required this.value, + required this.onToggle, + }); + + final String label; + final bool value; + final VoidCallback onToggle; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Material( + type: MaterialType.transparency, + child: CheckboxListTile( + value: value, + onChanged: (_) => onToggle(), + controlAffinity: ListTileControlAffinity.leading, + activeColor: const Color(0xDEFFFFFF), + checkColor: FccColors.gray90, + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: HTMLParser( + context: context, + ).parse(label, isSelectable: false), + ), + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_button.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_button.dart new file mode 100644 index 000000000..70f03bdcf --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_button.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; + +class ChallengeButton extends StatelessWidget { + const ChallengeButton({ + super.key, + required this.label, + required this.onPressed, + this.loading = false, + }); + + final String label; + final VoidCallback? onPressed; + final bool loading; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 50), + backgroundColor: FccColors.gray75, + side: const BorderSide(width: 2, color: FccColors.gray00), + shape: const RoundedRectangleBorder(), + ), + onPressed: loading ? null : onPressed, + child: loading + ? const SizedBox.square( + dimension: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Text(label, style: const TextStyle(fontSize: 20)), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_card.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_card.dart new file mode 100644 index 000000000..e7c994085 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_card.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; + +class ChallengeCard extends StatelessWidget { + const ChallengeCard({super.key, required this.title, required this.child}); + + final String title; + final Widget child; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(8), + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: FccColors.gray85, + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 14, left: 8, right: 8), + child: Text( + title.toUpperCase(), + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + child, + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_video.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_video.dart new file mode 100644 index 000000000..179cb2cba --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/challenge_video.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:youtube_player_iframe/youtube_player_iframe.dart'; + +class ChallengeVideo extends StatefulWidget { + const ChallengeVideo({super.key, required this.videoId}); + + final String videoId; + + @override + State createState() => _ChallengeVideoState(); +} + +class _ChallengeVideoState extends State { + late final _controller = YoutubePlayerController.fromVideoId( + videoId: widget.videoId, + params: const YoutubePlayerParams( + showFullscreenButton: true, + strictRelatedVideos: true, + origin: 'https://www.youtube-nocookie.com', + ), + ); + + @override + void dispose() { + _controller.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: YoutubePlayer( + controller: _controller, + enableFullScreenOnVerticalDrag: false, + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/complete_button.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/complete_button.dart new file mode 100644 index 000000000..85fc3445a --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/complete_button.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart' show GoRouterHelper; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_viewmodel.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_button.dart'; +import 'package:mobile_app_new/routing/learn.dart'; + +class CompleteButton extends ConsumerWidget { + const CompleteButton({ + super.key, + required this.challenge, + required this.block, + required this.label, + this.solution, + this.enabled = true, + }); + + final Challenge challenge; + final Block block; + final String label; + final String? solution; + final bool enabled; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final provider = challengeSubmitProvider(challenge.id); + final submit = ref.watch(provider); + + return Column( + children: [ + if (submit.hasError) + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Text( + 'Your progress could not be saved. Check your connection and try again.', + style: TextStyle(fontSize: 16, color: FccColors.red15), + ), + ), + ChallengeButton( + label: label, + loading: submit.isLoading, + onPressed: !enabled + ? null + : () async { + final saved = await ref + .read(provider.notifier) + .submit(challenge, solution: solution); + if (saved && context.mounted) _goToNext(context); + }, + ), + ], + ); + } + + void _goToNext(BuildContext context) { + final index = block.challenges.indexWhere((c) => c.id == challenge.id); + if (index == -1 || index == block.challenges.length - 1) { + context.pop(); + return; + } + + LearnChallengeRoute( + superBlock: block.superBlock, + block: block.dashedName, + challengeId: block.challenges[index + 1].id, + ).pushReplacement(context); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/example_editor.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/example_editor.dart new file mode 100644 index 000000000..7512705b1 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/example_editor.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; +import 'package:phone_ide/phone_ide.dart'; + +class ExampleEditor extends StatefulWidget { + const ExampleEditor({super.key, required this.nodules}); + + final List nodules; + + @override + State createState() => _ExampleEditorState(); +} + +class _ExampleEditorState extends State { + Widget? _card; + + @override + Widget build(BuildContext context) => _card ??= _build(context); + + Widget _build(BuildContext context) { + final parser = HTMLParser(context: context); + + return ChallengeCard( + title: 'Lesson', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final nodule in widget.nodules) + ...switch (nodule) { + ParagraphNodule(:final contents) => parser.parse( + contents, + customStyles: { + '*': Style(fontSize: FontSize(18)), + 'ul': Style( + fontSize: FontSize(18), + padding: HtmlPaddings.only(left: 10), + ), + }, + ), + InteractiveEditorNodule(:final files) => [ + for (final file in files) + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + file.ext.toUpperCase(), + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 8), + Editor( + options: EditorOptions( + fontFamily: 'Hack', + takeFullHeight: false, + showLinebar: false, + isEditable: false, + ), + defaultLanguage: file.ext, + defaultValue: file.contents, + path: '/', + ), + ], + ), + ), + ], + }, + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/help_dialog.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/help_dialog.dart new file mode 100644 index 000000000..77f3a2f61 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/help_dialog.dart @@ -0,0 +1,285 @@ +import 'dart:io'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/curriculum_model.dart'; +import 'package:url_launcher/url_launcher.dart'; + +const _forum = 'https://forum.freecodecamp.org'; +const _readSearchAsk = + '$_forum/t/how-to-get-help-when-you-are-stuck-coding/19514'; +const _minDescription = 50; +// NOTE: Above this the forum rejects the URL, so the post body falls back to a shorter template. +const _maxUrlLength = 8000; + +Future showHelpDialog( + BuildContext context, + Challenge challenge, + Block block, +) async { + final search = Uri.parse( + '$_forum/search?q=${Uri.encodeComponent('${block.name} - ${challenge.title} in:title')}', + ); + + final proceed = await showDialog( + context: context, + builder: (context) => _HelpDialog(search: search), + ); + if (proceed != true || !context.mounted) return; + + final description = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => _CreatePostDialog(search: search), + ); + if (description == null) return; + + await launchUrl(_forumPostUrl(challenge, block, description)); +} + +Uri _forumPostUrl(Challenge challenge, Block block, String description) { + final title = '${block.name} - ${challenge.title}'; + final ending = + '**Your mobile information:**\n```txt\n${Platform.operatingSystem} ${Platform.operatingSystemVersion}\n```\n\n' + '**Challenge:** $title\n\n' + '**Link to the challenge:**\n' + 'https://www.freecodecamp.org/learn/${challenge.superBlock}/${challenge.block}/${challenge.dashedName}'; + + final body = + "**Tell us what's happening:**\nDescribe your issue in detail here. \n\n$description \n\n$ending"; + final shortBody = "**Tell us what's happening:**\n\n\n\n$ending"; + + final base = + '$_forum/new-topic?category=${Uri.encodeComponent(challenge.helpCategory)}&title=${Uri.encodeComponent(title)}&body='; + final url = '$base${Uri.encodeComponent(body)}'; + + return Uri.parse( + url.length < _maxUrlLength ? url : '$base${Uri.encodeComponent(shortBody)}', + ); +} + +TextSpan _link(String text, Uri url) => TextSpan( + text: text, + style: const TextStyle( + color: FccColors.gray00, + decoration: TextDecoration.underline, + decorationColor: FccColors.gray00, + ), + recognizer: TapGestureRecognizer()..onTap = () => launchUrl(url), +); + +class _DialogButton extends StatelessWidget { + const _DialogButton({required this.label, required this.onPressed}); + + final String label; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: const Size(double.infinity, 50), + backgroundColor: FccColors.gray75, + foregroundColor: FccColors.gray00, + side: const BorderSide(width: 2, color: FccColors.gray00), + shape: const RoundedRectangleBorder(), + ), + onPressed: onPressed, + child: Text( + label, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ); + } +} + +class _HelpDialog extends StatelessWidget { + const _HelpDialog({required this.search}); + + final Uri search; + + @override + Widget build(BuildContext context) { + const body = TextStyle(fontSize: 16, color: FccColors.gray05); + + return Dialog( + backgroundColor: FccColors.gray90, + shape: const RoundedRectangleBorder(), + child: Padding( + padding: const EdgeInsets.all(16), + child: Wrap( + runSpacing: 20, + children: [ + const Text( + 'Ask for Help', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + Text.rich( + TextSpan( + style: body, + children: [ + const TextSpan(text: "If you've already tried the "), + _link('Read-Search-Ask', Uri.parse(_readSearchAsk)), + const TextSpan( + text: + ' method, then you can ask for help on the freeCodeCamp forum.', + ), + ], + ), + ), + Text.rich( + TextSpan( + style: body, + children: [ + const TextSpan(text: 'Before making a new post please '), + _link( + 'check if your question has already been answered on the forum', + search, + ), + const TextSpan(text: '.'), + ], + ), + ), + _DialogButton( + label: 'Create a post', + onPressed: () => Navigator.of(context).pop(true), + ), + _DialogButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(false), + ), + ], + ), + ), + ); + } +} + +class _CreatePostDialog extends StatefulWidget { + const _CreatePostDialog({required this.search}); + + final Uri search; + + @override + State<_CreatePostDialog> createState() => _CreatePostDialogState(); +} + +class _CreatePostDialogState extends State<_CreatePostDialog> { + final _description = TextEditingController(); + bool _triedReadSearchAsk = false; + bool _searched = false; + + @override + void dispose() { + _description.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final remaining = _minDescription - _description.text.length; + final canSubmit = _triedReadSearchAsk && _searched && remaining <= 0; + + return Dialog( + backgroundColor: FccColors.gray90, + shape: const RoundedRectangleBorder(), + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Wrap( + runSpacing: 20, + children: [ + const Text( + 'Create a post', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const Text( + 'You must confirm the following statements before you can submit your post to the forum.', + style: TextStyle(fontSize: 16, color: FccColors.gray05), + ), + _Statement( + value: _triedReadSearchAsk, + onChanged: (v) => setState(() => _triedReadSearchAsk = v), + text: TextSpan( + children: [ + const TextSpan(text: 'I have tried the '), + _link('Read-Search-Ask', Uri.parse(_readSearchAsk)), + const TextSpan(text: ' method'), + ], + ), + ), + _Statement( + value: _searched, + onChanged: (v) => setState(() => _searched = v), + text: TextSpan( + children: [ + const TextSpan(text: 'I have searched for '), + _link( + 'similar questions that have already been answered on the forum', + widget.search, + ), + ], + ), + ), + if (remaining > 0) + Text( + 'Please enter at least $remaining more characters.', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextField( + controller: _description, + onChanged: (_) => setState(() {}), + maxLines: 5, + decoration: const InputDecoration( + hintText: 'Describe your issue in detail here...', + border: OutlineInputBorder(), + ), + ), + _DialogButton( + label: 'Submit', + onPressed: canSubmit + ? () => Navigator.of(context).pop(_description.text) + : null, + ), + _DialogButton( + label: 'Cancel', + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + ); + } +} + +class _Statement extends StatelessWidget { + const _Statement({ + required this.value, + required this.onChanged, + required this.text, + }); + + final bool value; + final ValueChanged onChanged; + final TextSpan text; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Checkbox( + value: value, + onChanged: (v) => onChanged(v ?? false), + activeColor: FccColors.gray00, + ), + Flexible( + child: Text.rich( + text, + style: const TextStyle(fontSize: 14, color: FccColors.gray00), + ), + ), + ], + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/html_card.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/html_card.dart new file mode 100644 index 000000000..8fddbdba5 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/html_card.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/parsed_html.dart'; + +class HtmlCard extends StatelessWidget { + const HtmlCard({ + super.key, + required this.title, + required this.html, + this.collapsible = false, + }); + + final String title; + final String html; + final bool collapsible; + + @override + Widget build(BuildContext context) { + return ChallengeCard( + title: title, + child: collapsible + ? ExpansionTile( + title: const Text('Tap to expand'), + shape: const Border(), + collapsedShape: const Border(), + children: [ParsedHtml(html)], + ) + : ParsedHtml(html), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/parsed_html.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/parsed_html.dart new file mode 100644 index 000000000..420a8501e --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/parsed_html.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; + +// NOTE: Parses once. Re-parsing on every parent rebuild recreates the code editors, +// and their re-measuring jumps the scroll position. +class ParsedHtml extends StatefulWidget { + const ParsedHtml(this.html, {super.key, this.customStyles = const {}}); + + final String html; + final Map customStyles; + + @override + State createState() => _ParsedHtmlState(); +} + +class _ParsedHtmlState extends State { + List? _children; + + @override + void didUpdateWidget(ParsedHtml oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.html != widget.html) _children = null; + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _children ??= HTMLParser( + context: context, + ).parse(widget.html, customStyles: widget.customStyles), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/questions.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/questions.dart new file mode 100644 index 000000000..084640e4b --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/questions.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/quiz_audio_player.dart'; +import 'package:mobile_app_new/widgets/html_handler/html_handler.dart'; + +typedef QuestionItem = ({ + String text, + List answers, + // NOTE: 0-based index into answers. + int solution, + QuizAudioData? audioData, +}); + +QuestionItem questionItem(Question q) => ( + text: q.text, + answers: q.answers, + solution: q.solution - 1, + audioData: null, +); + +class Questions extends StatefulWidget { + const Questions({ + super.key, + required this.questions, + required this.selected, + required this.validated, + required this.onChanged, + }); + + final List questions; + final List selected; + final bool validated; + final void Function(int question, int answer) onChanged; + + @override + State createState() => _QuestionsState(); +} + +class _QuestionsState extends State { + late List> _texts; + late List>> _answers; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _parse(); + } + + @override + void didUpdateWidget(Questions oldWidget) { + super.didUpdateWidget(oldWidget); + if (!identical(oldWidget.questions, widget.questions)) _parse(); + } + + void _parse() { + final parser = HTMLParser(context: context); + _texts = [for (final q in widget.questions) parser.parse(q.text)]; + _answers = [ + for (final q in widget.questions) + [ + for (final a in q.answers) + parser.parse( + a.answer, + isSelectable: false, + customStyles: {'p': Style(margin: Margins.zero)}, + ), + ], + ]; + } + + @override + Widget build(BuildContext context) { + final multiple = widget.questions.length > 1; + + return Column( + children: [ + for (final (i, question) in widget.questions.indexed) + ChallengeCard( + title: multiple ? 'Question ${i + 1}' : 'Question', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._texts[i], + if (question.audioData case final audio?) ...[ + const SizedBox(height: 16), + QuizAudioPlayer(audioData: audio), + ], + const SizedBox(height: 8), + RadioGroup( + groupValue: widget.selected[i], + onChanged: (answer) { + if (answer != null) widget.onChanged(i, answer); + }, + child: Column( + children: [ + for (final (j, answer) in question.answers.indexed) + _option(i, j, answer), + ], + ), + ), + ], + ), + ), + ], + ); + } + + Widget _option(int question, int index, Answer answer) { + final isSelected = widget.selected[question] == index; + final isCorrect = widget.questions[question].solution == index; + + return Container( + margin: const EdgeInsets.symmetric(vertical: 8), + child: Material( + child: RadioListTile( + value: index, + selected: isSelected, + tileColor: FccColors.gray90, + selectedTileColor: FccColors.gray90, + shape: const RoundedRectangleBorder( + side: BorderSide(color: Color(0xFFAAAAAA), width: 2), + ), + title: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 100), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: _answers[question][index], + ), + ), + subtitle: isSelected && widget.validated + ? _Feedback(isCorrect: isCorrect, feedback: answer.feedback) + : null, + ), + ), + ); + } +} + +class _Feedback extends StatelessWidget { + const _Feedback({required this.isCorrect, required this.feedback}); + + final bool isCorrect; + final String? feedback; + + @override + Widget build(BuildContext context) { + final color = isCorrect ? FccColors.green40 : FccColors.red15; + final feedback = this.feedback ?? ''; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 12, bottom: feedback.isEmpty ? 24 : 0), + child: Text( + isCorrect ? 'Correct!' : 'Incorrect!', + style: TextStyle( + color: color, + fontWeight: FontWeight.bold, + fontSize: 18, + ), + ), + ), + if (feedback.isNotEmpty) + ...HTMLParser(context: context).parse( + feedback, + customStyles: {'*:not(pre):not(code)': Style(color: color)}, + ), + ], + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart new file mode 100644 index 000000000..7efee1edf --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart @@ -0,0 +1,164 @@ +import 'package:audio_service/audio_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; + +class QuizAudioPlayer extends ConsumerStatefulWidget { + const QuizAudioPlayer({super.key, required this.audioData}); + + final QuizAudioData audioData; + + @override + ConsumerState createState() => _QuizAudioPlayerState(); +} + +class _QuizAudioPlayerState extends ConsumerState { + bool _loading = false; + bool _failed = false; + + late final _handler = ref.read(audioHandlerProvider); + + AudioClip get _clip => widget.audioData.audio; + + @override + void dispose() { + if (_handler.curriculumClipId == _clip.id) _handler.stop(); + super.dispose(); + } + + Future _loadAndPlay() async { + setState(() => _loading = true); + + final loaded = await _handler.loadCurriculumAudio( + _clip.url, + clipId: _clip.id, + title: 'Quiz audio', + start: _clip.start, + end: _clip.end, + ); + + if (!mounted) return; + setState(() { + _loading = false; + _failed = !loaded; + }); + if (loaded) await _handler.play(); + } + + @override + Widget build(BuildContext context) { + if (_failed) { + return const Padding( + padding: EdgeInsets.all(16), + child: Center(child: Text('Error loading audio')), + ); + } + + final handler = _handler; + + return Column( + children: [ + StreamBuilder( + stream: handler.playbackState, + builder: (context, snapshot) { + final state = snapshot.data ?? PlaybackState(); + final isActive = handler.curriculumClipId == _clip.id; + final completed = + state.processingState == AudioProcessingState.completed; + final playing = isActive && state.playing && !completed; + + return Column( + children: [ + StreamBuilder( + stream: handler.positionStream, + builder: (context, position) => _Progress( + position: isActive ? position.data : null, + total: isActive ? handler.duration : null, + ), + ), + if (_loading) + const Padding( + padding: EdgeInsets.all(8), + child: SizedBox.square( + dimension: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ) + else + IconButton( + tooltip: playing ? 'Pause' : 'Play', + icon: Icon(playing ? Icons.pause : Icons.play_arrow), + onPressed: () async { + if (!isActive) return _loadAndPlay(); + if (playing) return handler.pause(); + if (completed) await handler.seek(Duration.zero); + await handler.play(); + }, + ), + ], + ); + }, + ), + if (widget.audioData.transcript.isNotEmpty) ...[ + const SizedBox(height: 8), + ChallengeCard( + title: 'Transcript', + child: ExpansionTile( + title: const Text( + 'Show transcript', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + shape: const Border(), + collapsedShape: const Border(), + expandedCrossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (final line in widget.audioData.transcript) + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text.rich( + TextSpan( + style: const TextStyle( + fontSize: 14, + color: FccColors.gray05, + ), + children: [ + TextSpan( + text: '${line.character}: ', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: line.text), + ], + ), + ), + ), + ], + ), + ), + ], + ], + ); + } +} + +class _Progress extends StatelessWidget { + const _Progress({required this.position, required this.total}); + + final Duration? position; + final Duration? total; + + @override + Widget build(BuildContext context) { + final totalMs = total?.inMilliseconds ?? 0; + final positionMs = position?.inMilliseconds ?? 0; + + return LinearProgressIndicator( + backgroundColor: FccColors.gray75, + valueColor: const AlwaysStoppedAnimation(FccColors.blue50), + value: totalMs == 0 ? 0 : (positionMs / totalMs).clamp(0.0, 1.0), + minHeight: 8, + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_canvas.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_canvas.dart new file mode 100644 index 000000000..d2aeebe1c --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_canvas.dart @@ -0,0 +1,152 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_viewmodel.dart'; + +class SceneCanvas extends ConsumerWidget { + const SceneCanvas({ + super.key, + required this.scene, + required this.title, + this.captionsBottom = 0, + }); + + final Scene scene; + final String title; + final double captionsBottom; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(sceneProvider(scene, title)); + final assets = ref.watch(sceneAssetsProvider).value; + final line = state.line; + // NOTE: Lower z is further back, so it paints first. + final characters = [...state.characters] + ..sort((a, b) => b.position.z.compareTo(a.position.z)); + + return LayoutBuilder( + builder: (context, constraints) => Stack( + clipBehavior: Clip.hardEdge, + children: [ + Positioned.fill( + child: CachedNetworkImage( + imageUrl: sceneBackgroundUrl(assets, state.background), + fit: BoxFit.cover, + placeholder: (_, _) => const ColoredBox(color: FccColors.gray75), + errorWidget: (_, _, _) => const ColoredBox( + color: FccColors.gray75, + child: Center(child: Icon(Icons.broken_image, size: 48)), + ), + ), + ), + for (final character in characters) + _Character( + key: ValueKey(character.name), + state: character, + sprites: characterSprites(assets, character), + canvas: constraints.biggest, + ), + if (state.showCaptions && line != null) + Positioned( + left: 0, + right: 0, + bottom: captionsBottom, + child: _Caption(line: line), + ), + ], + ), + ); + } +} + +class _Caption extends StatelessWidget { + const _Caption({required this.line}); + + final SceneLine line; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.black.withValues(alpha: 0.7), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: line.align == 'right' + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + Text( + line.character, + style: const TextStyle( + color: FccColors.blue50, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + const SizedBox(height: 2), + Text( + line.text, + style: const TextStyle(color: FccColors.gray00, fontSize: 14), + ), + ], + ), + ); + } +} + +// NOTE: Positions are percentages of the canvas; z scales the character and orders the stack. +class _Character extends StatelessWidget { + const _Character({ + super.key, + required this.state, + required this.sprites, + required this.canvas, + }); + + final CharacterState state; + final CharacterSprites sprites; + final Size canvas; + + Widget _layer(String url) => Positioned.fill( + child: CachedNetworkImage( + imageUrl: url, + fit: BoxFit.contain, + placeholder: (_, _) => const SizedBox.expand(), + errorWidget: (_, _, _) => const SizedBox.shrink(), + ), + ); + + @override + Widget build(BuildContext context) { + final height = canvas.height * state.position.z.toDouble(); + final width = height * 2 / 3; + final left = state.position.x.toDouble() / 100 * canvas.width - width / 2; + final bottom = + -(height - canvas.height) / 2 - + state.position.y.toDouble() / 100 * canvas.height; + + return AnimatedPositioned( + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + left: left, + bottom: bottom, + width: width, + height: height, + child: AnimatedOpacity( + opacity: state.opacity, + duration: const Duration(milliseconds: 500), + child: Stack( + children: [ + _layer(sprites.base), + _layer(sprites.brows), + _layer(sprites.eyes), + if (sprites.glasses case final glasses?) _layer(glasses), + _layer(sprites.mouth), + ], + ), + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_controls.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_controls.dart new file mode 100644 index 000000000..38b48673d --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_controls.dart @@ -0,0 +1,118 @@ +import 'package:audio_service/audio_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_viewmodel.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; + +class SceneControls extends ConsumerWidget { + const SceneControls({ + super.key, + required this.scene, + required this.title, + this.onFullscreen, + this.fullscreen = false, + }); + + final Scene scene; + final String title; + final VoidCallback? onFullscreen; + final bool fullscreen; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final handler = ref.watch(audioHandlerProvider); + final notifier = ref.read(sceneProvider(scene, title).notifier); + final showCaptions = ref.watch( + sceneProvider(scene, title).select((s) => s.showCaptions), + ); + final active = ref.watch( + sceneProvider(scene, title).select((s) => s.isActive), + ); + final iconColor = fullscreen ? FccColors.gray00 : null; + + return StreamBuilder( + stream: handler.playbackState, + builder: (context, snapshot) { + final state = snapshot.data ?? PlaybackState(); + final completed = + active && state.processingState == AudioProcessingState.completed; + final playing = active && state.playing && !completed; + + final progress = StreamBuilder( + stream: handler.positionStream, + builder: (context, position) { + final total = handler.duration?.inMilliseconds ?? 0; + final current = position.data?.inMilliseconds ?? 0; + + return LinearProgressIndicator( + backgroundColor: FccColors.gray75, + valueColor: const AlwaysStoppedAnimation(FccColors.blue50), + value: !active || total == 0 + ? 0 + : (current / total).clamp(0.0, 1.0), + minHeight: fullscreen ? 6 : 8, + ); + }, + ); + + final play = IconButton( + tooltip: completed ? 'Replay' : (playing ? 'Pause' : 'Play'), + onPressed: playing ? notifier.pause : notifier.play, + icon: Icon( + completed + ? Icons.replay + : (playing ? Icons.pause : Icons.play_arrow), + color: iconColor, + size: fullscreen ? 32 : null, + ), + ); + + final captions = IconButton( + tooltip: showCaptions ? 'Hide captions' : 'Show captions', + onPressed: notifier.toggleCaptions, + icon: Icon( + showCaptions ? Icons.closed_caption : Icons.closed_caption_off, + color: iconColor, + size: fullscreen ? 28 : null, + ), + ); + + if (fullscreen) { + return Container( + color: Colors.black54, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: Row( + children: [ + play, + const SizedBox(width: 12), + Expanded(child: progress), + const SizedBox(width: 12), + captions, + ], + ), + ); + } + + return Column( + children: [ + progress, + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + play, + captions, + IconButton( + tooltip: 'Fullscreen', + onPressed: onFullscreen, + icon: const Icon(Icons.fullscreen), + ), + ], + ), + ], + ); + }, + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_view.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_view.dart new file mode 100644 index 000000000..9be8400f1 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_view.dart @@ -0,0 +1,71 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_canvas.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_controls.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_viewmodel.dart'; +import 'package:mobile_app_new/routing/learn.dart'; + +class SceneView extends ConsumerStatefulWidget { + const SceneView({super.key, required this.scene, required this.title}); + + final Scene scene; + final String title; + + @override + ConsumerState createState() => _SceneViewState(); +} + +class _SceneViewState extends ConsumerState { + bool _precached = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (_precached) return; + _precached = true; + + final assets = ref.read(sceneAssetsProvider).value; + final backgrounds = { + widget.scene.setup.background, + for (final c in widget.scene.commands) ?c.background, + }; + for (final bg in backgrounds) { + precacheImage( + CachedNetworkImageProvider(sceneBackgroundUrl(assets, bg)), + context, + ); + } + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), + child: Column( + children: [ + AspectRatio( + aspectRatio: 16 / 9, + child: DecoratedBox( + decoration: BoxDecoration( + color: Colors.black, + border: Border.all(color: FccColors.gray75, width: 2), + ), + child: SceneCanvas(scene: widget.scene, title: widget.title), + ), + ), + const SizedBox(height: 16), + SceneControls( + scene: widget.scene, + title: widget.title, + onFullscreen: () => LearnSceneFullscreenRoute( + $extra: (widget.scene, widget.title), + ).push(context), + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.dart new file mode 100644 index 000000000..298dfd8a8 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.dart @@ -0,0 +1,337 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:audio_service/audio_service.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/scene_assets_model.dart'; +import 'package:mobile_app_new/learn/services/curriculum_service.dart'; +import 'package:mobile_app_new/services/audio_service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'scene_viewmodel.freezed.dart'; +part 'scene_viewmodel.g.dart'; + +const _cdn = + 'https://cdn.freecodecamp.org/curriculum/english/animation-assets/images'; +const _mouthInterval = (min: 85, max: 105); +const _blinkInterval = (min: 2000, max: 4000); +const _blinkDuration = Duration(milliseconds: 150); + +@riverpod +Future sceneAssets(Ref ref) => + ref.watch(learnCurriculumServiceProvider).getSceneAssets(); + +@freezed +abstract class CharacterState with _$CharacterState { + const factory CharacterState({ + required String name, + required ScenePosition position, + @Default(false) bool mouthOpen, + @Default(false) bool eyesClosed, + @Default(1) double opacity, + }) = _CharacterState; +} + +typedef SceneLine = ({String character, String text, String align}); + +typedef CharacterSprites = ({ + String base, + String brows, + String eyes, + String? glasses, + String mouth, +}); + +@freezed +abstract class SceneState with _$SceneState { + const factory SceneState({ + required String background, + required List characters, + SceneLine? line, + @Default(false) bool showCaptions, + // NOTE: Whether the audio handler currently holds this scene's clip. + @Default(false) bool isActive, + }) = _SceneState; +} + +String sceneBackgroundUrl(SceneAssets? assets, String name) { + final file = name.endsWith('.png') ? name : '$name.png'; + return '${assets?.backgrounds ?? '$_cdn/backgrounds'}/$file'; +} + +// NOTE: Falls back to the CDN's folder layout when scene-assets.json hasn't loaded. +CharacterSprites characterSprites(SceneAssets? assets, CharacterState c) { + final a = assets?.characterAssets[c.name]; + String fallback(String file) => + '$_cdn/characters/${c.name.toLowerCase()}/$file'; + + return ( + base: a?.base ?? fallback('base.png'), + brows: a?.brows ?? fallback('brows.png'), + eyes: c.eyesClosed + ? a?.eyesClosed ?? fallback('eyes-closed.png') + : a?.eyesOpen ?? fallback('eyes-open.png'), + glasses: a?.glasses, + mouth: c.mouthOpen + ? a?.mouthOpen ?? fallback('mouth-open.png') + : a?.mouthClosed ?? fallback('mouth-closed.png'), + ); +} + +@riverpod +class SceneNotifier extends _$SceneNotifier { + late AudioPlayerHandler _handler; + + final _applied = {}; + final _mouthTimers = {}; + final _blinkTimers = {}; + final _random = Random(); + + bool _started = false; + bool _completed = false; + bool _playing = false; + + @override + SceneState build(Scene scene, String title) { + _handler = ref.read(audioHandlerProvider); + + final subs = [ + _handler.positionStream.listen(_onPosition), + _handler.playbackState.listen(_onPlayback), + ]; + ref.onDispose(() { + for (final sub in subs) { + sub.cancel(); + } + _stopTimers(_mouthTimers); + _stopTimers(_blinkTimers); + if (_isActive) _handler.stop(); + }); + + return _initial(); + } + + AudioClip get _clip => scene.setup.audio; + + double get _audioOffset => _clip.startTime.toDouble(); + + bool get _isActive => _handler.curriculumClipId == _clip.id; + + void toggleCaptions() => + state = state.copyWith(showCaptions: !state.showCaptions); + + Future play() async { + if (_started && !_completed && _isActive) return _handler.play(); + + if (!_isActive) { + final loaded = await _handler.loadCurriculumAudio( + _clip.url, + clipId: _clip.id, + title: title, + start: _clip.start, + end: _clip.end, + ); + if (!loaded || !ref.mounted) return; + } else if (_completed) { + await _handler.seek(Duration.zero); + if (!ref.mounted) return; + } + + _stopTimers(_mouthTimers); + _stopTimers(_blinkTimers); + _started = true; + _completed = false; + _applied.clear(); + + var next = _initial().copyWith( + showCaptions: state.showCaptions, + isActive: true, + ); + for (final (i, command) in scene.commands.indexed) { + if (command.startTime < _audioOffset) { + _applied.add(i); + next = _apply(next, command); + } + } + state = next; + + await Future.delayed( + Duration(milliseconds: (_audioOffset * 1000).round()), + ); + if (ref.mounted && _isActive) await _handler.play(); + } + + Future pause() => _handler.pause(); + + SceneState _initial() => SceneState( + background: scene.setup.background, + characters: [ + for (final c in scene.setup.characters) + CharacterState( + name: c.character, + position: c.position, + opacity: c.opacity?.toDouble() ?? 1, + ), + ], + ); + + void _onPlayback(PlaybackState playback) { + final active = _isActive; + if (active != state.isActive) state = state.copyWith(isActive: active); + if (!active) return; + + _playing = playback.playing; + if (_playing) { + for (final c in state.characters) { + _startBlinking(c.name); + } + } else { + _stopTimers(_mouthTimers); + _stopTimers(_blinkTimers); + state = state.copyWith( + characters: [ + for (final c in state.characters) + c.copyWith(mouthOpen: false, eyesClosed: false), + ], + ); + } + + if (playback.processingState == AudioProcessingState.completed && + _started && + !_completed) { + _complete(); + } + } + + void _complete() { + _completed = true; + var next = state.copyWith(line: null); + for (final (i, command) in scene.commands.indexed) { + if (_applied.add(i)) next = _apply(next, command); + } + state = next; + } + + void _onPosition(Duration position) { + if (!_isActive || !_playing || _completed) return; + + final seconds = position.inMilliseconds / 1000 + _audioOffset; + var next = state; + final speaking = {}; + SceneLine? active; + + for (final (i, command) in scene.commands.indexed) { + final start = command.startTime.toDouble(); + final finish = command.finishTime?.toDouble(); + + if (command.dialogue case final dialogue?) { + if (seconds >= start && (finish == null || seconds <= finish)) { + speaking.add(command.character); + active = ( + character: command.character, + text: dialogue.text, + align: dialogue.align, + ); + } + } + + if (seconds >= start && _applied.add(i)) next = _apply(next, command); + } + + if (active != null && active != next.line) { + next = next.copyWith(line: active); + } + state = next; + + for (final name in speaking) { + if (!_mouthTimers.containsKey(name)) _startTalking(name); + } + for (final name in [..._mouthTimers.keys]) { + if (!speaking.contains(name)) { + _mouthTimers.remove(name)?.cancel(); + _update(name, (c) => c.copyWith(mouthOpen: false)); + } + } + } + + SceneState _apply(SceneState current, SceneCommand command) { + final characters = [...current.characters]; + final index = characters.indexWhere((c) => c.name == command.character); + + if (index == -1) { + characters.add( + CharacterState( + name: command.character, + position: command.position ?? const ScenePosition(x: 0, y: 0, z: 0), + opacity: command.opacity?.toDouble() ?? 1, + ), + ); + } else if (command.position != null || command.opacity != null) { + final character = characters[index]; + characters[index] = character.copyWith( + position: command.position ?? character.position, + opacity: command.opacity?.toDouble() ?? character.opacity, + ); + } + + return current.copyWith( + background: command.background ?? current.background, + characters: characters, + ); + } + + int _between(({int min, int max}) range) => + range.min + _random.nextInt(range.max - range.min + 1); + + void _startTalking(String name) { + void toggle(bool open) { + _update(name, (c) => c.copyWith(mouthOpen: open)); + _mouthTimers[name] = Timer( + Duration(milliseconds: _between(_mouthInterval)), + () { + if (_mouthTimers.containsKey(name)) toggle(!open); + }, + ); + } + + toggle(true); + } + + void _startBlinking(String name) { + if (_blinkTimers.containsKey(name)) return; + + void schedule() { + _blinkTimers[name] = Timer( + Duration(milliseconds: _between(_blinkInterval)), + () { + _update(name, (c) => c.copyWith(eyesClosed: true)); + Timer(_blinkDuration, () { + if (_blinkTimers.containsKey(name)) { + _update(name, (c) => c.copyWith(eyesClosed: false)); + } + }); + if (_blinkTimers.containsKey(name)) schedule(); + }, + ); + } + + schedule(); + } + + void _stopTimers(Map timers) { + for (final timer in timers.values) { + timer.cancel(); + } + timers.clear(); + } + + void _update(String name, CharacterState Function(CharacterState) change) { + if (!ref.mounted) return; + state = state.copyWith( + characters: [ + for (final c in state.characters) c.name == name ? change(c) : c, + ], + ); + } +} diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.freezed.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.freezed.dart new file mode 100644 index 000000000..fd00c4c47 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.freezed.dart @@ -0,0 +1,576 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'scene_viewmodel.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$CharacterState { + + String get name; ScenePosition get position; bool get mouthOpen; bool get eyesClosed; double get opacity; +/// Create a copy of CharacterState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$CharacterStateCopyWith get copyWith => _$CharacterStateCopyWithImpl(this as CharacterState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is CharacterState&&(identical(other.name, name) || other.name == name)&&(identical(other.position, position) || other.position == position)&&(identical(other.mouthOpen, mouthOpen) || other.mouthOpen == mouthOpen)&&(identical(other.eyesClosed, eyesClosed) || other.eyesClosed == eyesClosed)&&(identical(other.opacity, opacity) || other.opacity == opacity)); +} + + +@override +int get hashCode => Object.hash(runtimeType,name,position,mouthOpen,eyesClosed,opacity); + +@override +String toString() { + return 'CharacterState(name: $name, position: $position, mouthOpen: $mouthOpen, eyesClosed: $eyesClosed, opacity: $opacity)'; +} + + +} + +/// @nodoc +abstract mixin class $CharacterStateCopyWith<$Res> { + factory $CharacterStateCopyWith(CharacterState value, $Res Function(CharacterState) _then) = _$CharacterStateCopyWithImpl; +@useResult +$Res call({ + String name, ScenePosition position, bool mouthOpen, bool eyesClosed, double opacity +}); + + +$ScenePositionCopyWith<$Res> get position; + +} +/// @nodoc +class _$CharacterStateCopyWithImpl<$Res> + implements $CharacterStateCopyWith<$Res> { + _$CharacterStateCopyWithImpl(this._self, this._then); + + final CharacterState _self; + final $Res Function(CharacterState) _then; + +/// Create a copy of CharacterState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? position = null,Object? mouthOpen = null,Object? eyesClosed = null,Object? opacity = null,}) { + return _then(_self.copyWith( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,position: null == position ? _self.position : position // ignore: cast_nullable_to_non_nullable +as ScenePosition,mouthOpen: null == mouthOpen ? _self.mouthOpen : mouthOpen // ignore: cast_nullable_to_non_nullable +as bool,eyesClosed: null == eyesClosed ? _self.eyesClosed : eyesClosed // ignore: cast_nullable_to_non_nullable +as bool,opacity: null == opacity ? _self.opacity : opacity // ignore: cast_nullable_to_non_nullable +as double, + )); +} +/// Create a copy of CharacterState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ScenePositionCopyWith<$Res> get position { + + return $ScenePositionCopyWith<$Res>(_self.position, (value) { + return _then(_self.copyWith(position: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [CharacterState]. +extension CharacterStatePatterns on CharacterState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _CharacterState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _CharacterState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _CharacterState value) $default,){ +final _that = this; +switch (_that) { +case _CharacterState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _CharacterState value)? $default,){ +final _that = this; +switch (_that) { +case _CharacterState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String name, ScenePosition position, bool mouthOpen, bool eyesClosed, double opacity)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _CharacterState() when $default != null: +return $default(_that.name,_that.position,_that.mouthOpen,_that.eyesClosed,_that.opacity);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String name, ScenePosition position, bool mouthOpen, bool eyesClosed, double opacity) $default,) {final _that = this; +switch (_that) { +case _CharacterState(): +return $default(_that.name,_that.position,_that.mouthOpen,_that.eyesClosed,_that.opacity);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String name, ScenePosition position, bool mouthOpen, bool eyesClosed, double opacity)? $default,) {final _that = this; +switch (_that) { +case _CharacterState() when $default != null: +return $default(_that.name,_that.position,_that.mouthOpen,_that.eyesClosed,_that.opacity);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _CharacterState implements CharacterState { + const _CharacterState({required this.name, required this.position, this.mouthOpen = false, this.eyesClosed = false, this.opacity = 1}); + + +@override final String name; +@override final ScenePosition position; +@override@JsonKey() final bool mouthOpen; +@override@JsonKey() final bool eyesClosed; +@override@JsonKey() final double opacity; + +/// Create a copy of CharacterState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$CharacterStateCopyWith<_CharacterState> get copyWith => __$CharacterStateCopyWithImpl<_CharacterState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _CharacterState&&(identical(other.name, name) || other.name == name)&&(identical(other.position, position) || other.position == position)&&(identical(other.mouthOpen, mouthOpen) || other.mouthOpen == mouthOpen)&&(identical(other.eyesClosed, eyesClosed) || other.eyesClosed == eyesClosed)&&(identical(other.opacity, opacity) || other.opacity == opacity)); +} + + +@override +int get hashCode => Object.hash(runtimeType,name,position,mouthOpen,eyesClosed,opacity); + +@override +String toString() { + return 'CharacterState(name: $name, position: $position, mouthOpen: $mouthOpen, eyesClosed: $eyesClosed, opacity: $opacity)'; +} + + +} + +/// @nodoc +abstract mixin class _$CharacterStateCopyWith<$Res> implements $CharacterStateCopyWith<$Res> { + factory _$CharacterStateCopyWith(_CharacterState value, $Res Function(_CharacterState) _then) = __$CharacterStateCopyWithImpl; +@override @useResult +$Res call({ + String name, ScenePosition position, bool mouthOpen, bool eyesClosed, double opacity +}); + + +@override $ScenePositionCopyWith<$Res> get position; + +} +/// @nodoc +class __$CharacterStateCopyWithImpl<$Res> + implements _$CharacterStateCopyWith<$Res> { + __$CharacterStateCopyWithImpl(this._self, this._then); + + final _CharacterState _self; + final $Res Function(_CharacterState) _then; + +/// Create a copy of CharacterState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? position = null,Object? mouthOpen = null,Object? eyesClosed = null,Object? opacity = null,}) { + return _then(_CharacterState( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,position: null == position ? _self.position : position // ignore: cast_nullable_to_non_nullable +as ScenePosition,mouthOpen: null == mouthOpen ? _self.mouthOpen : mouthOpen // ignore: cast_nullable_to_non_nullable +as bool,eyesClosed: null == eyesClosed ? _self.eyesClosed : eyesClosed // ignore: cast_nullable_to_non_nullable +as bool,opacity: null == opacity ? _self.opacity : opacity // ignore: cast_nullable_to_non_nullable +as double, + )); +} + +/// Create a copy of CharacterState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$ScenePositionCopyWith<$Res> get position { + + return $ScenePositionCopyWith<$Res>(_self.position, (value) { + return _then(_self.copyWith(position: value)); + }); +} +} + +/// @nodoc +mixin _$SceneState { + + String get background; List get characters; SceneLine? get line; bool get showCaptions; bool get isActive; +/// Create a copy of SceneState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SceneStateCopyWith get copyWith => _$SceneStateCopyWithImpl(this as SceneState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SceneState&&(identical(other.background, background) || other.background == background)&&const DeepCollectionEquality().equals(other.characters, characters)&&(identical(other.line, line) || other.line == line)&&(identical(other.showCaptions, showCaptions) || other.showCaptions == showCaptions)&&(identical(other.isActive, isActive) || other.isActive == isActive)); +} + + +@override +int get hashCode => Object.hash(runtimeType,background,const DeepCollectionEquality().hash(characters),line,showCaptions,isActive); + +@override +String toString() { + return 'SceneState(background: $background, characters: $characters, line: $line, showCaptions: $showCaptions, isActive: $isActive)'; +} + + +} + +/// @nodoc +abstract mixin class $SceneStateCopyWith<$Res> { + factory $SceneStateCopyWith(SceneState value, $Res Function(SceneState) _then) = _$SceneStateCopyWithImpl; +@useResult +$Res call({ + String background, List characters, SceneLine? line, bool showCaptions, bool isActive +}); + + + + +} +/// @nodoc +class _$SceneStateCopyWithImpl<$Res> + implements $SceneStateCopyWith<$Res> { + _$SceneStateCopyWithImpl(this._self, this._then); + + final SceneState _self; + final $Res Function(SceneState) _then; + +/// Create a copy of SceneState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? background = null,Object? characters = null,Object? line = freezed,Object? showCaptions = null,Object? isActive = null,}) { + return _then(_self.copyWith( +background: null == background ? _self.background : background // ignore: cast_nullable_to_non_nullable +as String,characters: null == characters ? _self.characters : characters // ignore: cast_nullable_to_non_nullable +as List,line: freezed == line ? _self.line : line // ignore: cast_nullable_to_non_nullable +as SceneLine?,showCaptions: null == showCaptions ? _self.showCaptions : showCaptions // ignore: cast_nullable_to_non_nullable +as bool,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SceneState]. +extension SceneStatePatterns on SceneState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SceneState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SceneState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SceneState value) $default,){ +final _that = this; +switch (_that) { +case _SceneState(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SceneState value)? $default,){ +final _that = this; +switch (_that) { +case _SceneState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String background, List characters, SceneLine? line, bool showCaptions, bool isActive)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SceneState() when $default != null: +return $default(_that.background,_that.characters,_that.line,_that.showCaptions,_that.isActive);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String background, List characters, SceneLine? line, bool showCaptions, bool isActive) $default,) {final _that = this; +switch (_that) { +case _SceneState(): +return $default(_that.background,_that.characters,_that.line,_that.showCaptions,_that.isActive);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String background, List characters, SceneLine? line, bool showCaptions, bool isActive)? $default,) {final _that = this; +switch (_that) { +case _SceneState() when $default != null: +return $default(_that.background,_that.characters,_that.line,_that.showCaptions,_that.isActive);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _SceneState implements SceneState { + const _SceneState({required this.background, required final List characters, this.line, this.showCaptions = false, this.isActive = false}): _characters = characters; + + +@override final String background; + final List _characters; +@override List get characters { + if (_characters is EqualUnmodifiableListView) return _characters; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_characters); +} + +@override final SceneLine? line; +@override@JsonKey() final bool showCaptions; +@override@JsonKey() final bool isActive; + +/// Create a copy of SceneState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SceneStateCopyWith<_SceneState> get copyWith => __$SceneStateCopyWithImpl<_SceneState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SceneState&&(identical(other.background, background) || other.background == background)&&const DeepCollectionEquality().equals(other._characters, _characters)&&(identical(other.line, line) || other.line == line)&&(identical(other.showCaptions, showCaptions) || other.showCaptions == showCaptions)&&(identical(other.isActive, isActive) || other.isActive == isActive)); +} + + +@override +int get hashCode => Object.hash(runtimeType,background,const DeepCollectionEquality().hash(_characters),line,showCaptions,isActive); + +@override +String toString() { + return 'SceneState(background: $background, characters: $characters, line: $line, showCaptions: $showCaptions, isActive: $isActive)'; +} + + +} + +/// @nodoc +abstract mixin class _$SceneStateCopyWith<$Res> implements $SceneStateCopyWith<$Res> { + factory _$SceneStateCopyWith(_SceneState value, $Res Function(_SceneState) _then) = __$SceneStateCopyWithImpl; +@override @useResult +$Res call({ + String background, List characters, SceneLine? line, bool showCaptions, bool isActive +}); + + + + +} +/// @nodoc +class __$SceneStateCopyWithImpl<$Res> + implements _$SceneStateCopyWith<$Res> { + __$SceneStateCopyWithImpl(this._self, this._then); + + final _SceneState _self; + final $Res Function(_SceneState) _then; + +/// Create a copy of SceneState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? background = null,Object? characters = null,Object? line = freezed,Object? showCaptions = null,Object? isActive = null,}) { + return _then(_SceneState( +background: null == background ? _self.background : background // ignore: cast_nullable_to_non_nullable +as String,characters: null == characters ? _self._characters : characters // ignore: cast_nullable_to_non_nullable +as List,line: freezed == line ? _self.line : line // ignore: cast_nullable_to_non_nullable +as SceneLine?,showCaptions: null == showCaptions ? _self.showCaptions : showCaptions // ignore: cast_nullable_to_non_nullable +as bool,isActive: null == isActive ? _self.isActive : isActive // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + +// dart format on diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.g.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.g.dart new file mode 100644 index 000000000..402a30da8 --- /dev/null +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.g.dart @@ -0,0 +1,147 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'scene_viewmodel.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(sceneAssets) +final sceneAssetsProvider = SceneAssetsProvider._(); + +final class SceneAssetsProvider + extends + $FunctionalProvider< + AsyncValue, + SceneAssets, + FutureOr + > + with $FutureModifier, $FutureProvider { + SceneAssetsProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'sceneAssetsProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$sceneAssetsHash(); + + @$internal + @override + $FutureProviderElement $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr create(Ref ref) { + return sceneAssets(ref); + } +} + +String _$sceneAssetsHash() => r'9a145ce15f3d4c4f8804542aa944b475e0a3ec7b'; + +@ProviderFor(SceneNotifier) +final sceneProvider = SceneNotifierFamily._(); + +final class SceneNotifierProvider + extends $NotifierProvider { + SceneNotifierProvider._({ + required SceneNotifierFamily super.from, + required (Scene, String) super.argument, + }) : super( + retry: null, + name: r'sceneProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$sceneNotifierHash(); + + @override + String toString() { + return r'sceneProvider' + '' + '$argument'; + } + + @$internal + @override + SceneNotifier create() => SceneNotifier(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(SceneState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } + + @override + bool operator ==(Object other) { + return other is SceneNotifierProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$sceneNotifierHash() => r'5055d4f5b93cc65cf31cd2581ead02f2273194af'; + +final class SceneNotifierFamily extends $Family + with + $ClassFamilyOverride< + SceneNotifier, + SceneState, + SceneState, + SceneState, + (Scene, String) + > { + SceneNotifierFamily._() + : super( + retry: null, + name: r'sceneProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + SceneNotifierProvider call(Scene scene, String title) => + SceneNotifierProvider._(argument: (scene, title), from: this); + + @override + String toString() => r'sceneProvider'; +} + +abstract class _$SceneNotifier extends $Notifier { + late final _$args = ref.$arg as (Scene, String); + Scene get scene => _$args.$1; + String get title => _$args.$2; + + SceneState build(Scene scene, String title); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + SceneState, + Object?, + Object? + >; + return element.handleCreate(ref, () => build(_$args.$1, _$args.$2)); + } +} diff --git a/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart b/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart new file mode 100644 index 000000000..822fbaf9b --- /dev/null +++ b/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:go_router/go_router.dart'; +import 'package:mobile_app_new/fcc_theme.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_canvas.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_controls.dart'; + +class SceneFullscreenView extends StatefulWidget { + const SceneFullscreenView({ + super.key, + required this.scene, + required this.title, + }); + + final Scene scene; + final String title; + + @override + State createState() => _SceneFullscreenViewState(); +} + +class _SceneFullscreenViewState extends State { + final _controlsKey = GlobalKey(); + double _controlsHeight = 0; + + @override + void initState() { + super.initState(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); + WidgetsBinding.instance.addPostFrameCallback((_) { + final box = _controlsKey.currentContext?.findRenderObject() as RenderBox?; + if (mounted && box != null && box.hasSize) { + setState(() => _controlsHeight = box.size.height); + } + }); + } + + @override + void dispose() { + SystemChrome.setPreferredOrientations([]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + Positioned.fill( + child: SceneCanvas( + scene: widget.scene, + title: widget.title, + captionsBottom: _controlsHeight, + ), + ), + Positioned( + top: 16, + right: 16, + child: DecoratedBox( + decoration: BoxDecoration( + color: Colors.black54, + borderRadius: BorderRadius.circular(4), + ), + child: IconButton( + tooltip: 'Exit fullscreen', + icon: const Icon( + Icons.fullscreen_exit, + color: FccColors.gray00, + size: 32, + ), + onPressed: () => context.pop(), + ), + ), + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: SceneControls( + key: _controlsKey, + scene: widget.scene, + title: widget.title, + fullscreen: true, + ), + ), + ], + ), + ); + } +} diff --git a/mobile-app-new/lib/routing/learn.dart b/mobile-app-new/lib/routing/learn.dart index fdedfd653..5b13d3aa7 100644 --- a/mobile-app-new/lib/routing/learn.dart +++ b/mobile-app-new/lib/routing/learn.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/learn/ui/challenge/challenge_placeholder_view.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_view.dart'; +import 'package:mobile_app_new/learn/models/challenge_model.dart'; import 'package:mobile_app_new/learn/ui/daily_challenge_archive/daily_challenge_archive_view.dart'; import 'package:mobile_app_new/learn/ui/landing/landing_view.dart'; import 'package:mobile_app_new/learn/ui/module/module_view.dart'; +import 'package:mobile_app_new/learn/ui/scene_fullscreen/scene_fullscreen_view.dart'; import 'package:mobile_app_new/learn/ui/superblock/superblock_view.dart'; part 'learn.g.dart'; @@ -11,6 +14,7 @@ part 'learn.g.dart'; const learnLandingPath = '/learn'; const learnDailyChallengeArchivePath = '/learn/daily-challenges'; const learnDailyChallengePath = '/learn/daily-challenges/:day'; +const learnSceneFullscreenPath = '/learn/scene-fullscreen'; const learnSuperBlockPath = '/learn/:superBlock'; const learnModulePath = '/learn/:superBlock/:module'; const learnChallengePath = '/learn/:superBlock/:block/:challengeId'; @@ -50,6 +54,24 @@ class LearnDailyChallengeRoute extends GoRouteData ChallengePlaceholderView(details: 'Daily challenge $day'); } +@TypedGoRoute(path: learnSceneFullscreenPath) +class LearnSceneFullscreenRoute extends GoRouteData + with $LearnSceneFullscreenRoute { + const LearnSceneFullscreenRoute({required this.$extra}); + + final (Scene, String) $extra; + + @override + Page buildPage(BuildContext context, GoRouterState state) => + CustomTransitionPage( + key: state.pageKey, + opaque: false, + child: SceneFullscreenView(scene: $extra.$1, title: $extra.$2), + transitionsBuilder: (_, animation, _, child) => + FadeTransition(opacity: animation, child: child), + ); +} + @TypedGoRoute(path: learnSuperBlockPath) class LearnSuperBlockRoute extends GoRouteData with $LearnSuperBlockRoute { const LearnSuperBlockRoute({required this.superBlock}); @@ -73,7 +95,6 @@ class LearnModuleRoute extends GoRouteData with $LearnModuleRoute { ModuleView(superBlock: superBlock, module: module); } -// TODO: Iterations 3 and 4 replace the placeholder with the challenge views. @TypedGoRoute(path: learnChallengePath) class LearnChallengeRoute extends GoRouteData with $LearnChallengeRoute { const LearnChallengeRoute({ @@ -87,6 +108,9 @@ class LearnChallengeRoute extends GoRouteData with $LearnChallengeRoute { final String challengeId; @override - Widget build(BuildContext context, GoRouterState state) => - ChallengePlaceholderView(details: '$superBlock/$block/$challengeId'); + Widget build(BuildContext context, GoRouterState state) => ChallengeView( + superBlock: superBlock, + block: block, + challengeId: challengeId, + ); } diff --git a/mobile-app-new/lib/routing/learn.g.dart b/mobile-app-new/lib/routing/learn.g.dart index bd12bbbc7..cec2f631a 100644 --- a/mobile-app-new/lib/routing/learn.g.dart +++ b/mobile-app-new/lib/routing/learn.g.dart @@ -10,6 +10,7 @@ List get $appRoutes => [ $learnLandingRoute, $learnDailyChallengeArchiveRoute, $learnDailyChallengeRoute, + $learnSceneFullscreenRoute, $learnSuperBlockRoute, $learnModuleRoute, $learnChallengeRoute, @@ -100,6 +101,37 @@ mixin $LearnDailyChallengeRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +RouteBase get $learnSceneFullscreenRoute => GoRouteData.$route( + path: '/learn/scene-fullscreen', + hasOverriddenOnExit: false, + factory: $LearnSceneFullscreenRoute._fromState, +); + +mixin $LearnSceneFullscreenRoute on GoRouteData { + static LearnSceneFullscreenRoute _fromState(GoRouterState state) => + LearnSceneFullscreenRoute($extra: state.extra as (Scene, String)); + + LearnSceneFullscreenRoute get _self => this as LearnSceneFullscreenRoute; + + @override + String get location => GoRouteData.$location('/learn/scene-fullscreen'); + + @override + void go(BuildContext context) => context.go(location, extra: _self.$extra); + + @override + Future push(BuildContext context) => + context.push(location, extra: _self.$extra); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: _self.$extra); + + @override + void replace(BuildContext context) => + context.replace(location, extra: _self.$extra); +} + RouteBase get $learnSuperBlockRoute => GoRouteData.$route( path: '/learn/:superBlock', hasOverriddenOnExit: false, diff --git a/mobile-app-new/lib/services/audio_service.dart b/mobile-app-new/lib/services/audio_service.dart index 909e4f76d..989108650 100644 --- a/mobile-app-new/lib/services/audio_service.dart +++ b/mobile-app-new/lib/services/audio_service.dart @@ -31,6 +31,12 @@ class PodcastAudioConfig extends AudioTypeConfig { final String episodeId; } +class CurriculumAudioConfig extends AudioTypeConfig { + const CurriculumAudioConfig(this.clipId); + + final String clipId; +} + // NOTE: The handler is created before runApp and injected, so reading // this without that override is a wiring mistake rather than a lazy init. @Riverpod(keepAlive: true) @@ -82,6 +88,11 @@ class AudioPlayerHandler extends BaseAudioHandler { _ => null, }; + String? get curriculumClipId => switch (_audioConfig) { + CurriculumAudioConfig(:final clipId) => clipId, + _ => null, + }; + Stream get positionStream => _audioPlayer.positionStream; Duration? get duration => _audioPlayer.duration; @@ -173,7 +184,7 @@ class AudioPlayerHandler extends BaseAudioHandler { _audioConfig = const CodeRadioAudioConfig(); await _audioPlayer.setSpeed(_defaultSpeed); - await _audioPlayer.setAudioSource(AudioSource.uri(url, tag: song)); + await _audioPlayer.setAudioSource(AudioSource.uri(url)); _publish(song); } catch (e) { @@ -186,12 +197,10 @@ class AudioPlayerHandler extends BaseAudioHandler { Future _rejoinCodeRadioLiveEdge() async { final url = _codeRadioUrl; - final song = mediaItem.value; - - if (url == null || song == null) return; + if (url == null) return; try { - await _audioPlayer.setAudioSource(AudioSource.uri(url, tag: song)); + await _audioPlayer.setAudioSource(AudioSource.uri(url)); } catch (e) { log('rejoinCodeRadioLiveEdge: Cannot reconnect: $e'); } @@ -218,7 +227,7 @@ class AudioPlayerHandler extends BaseAudioHandler { try { await _audioPlayer.setSpeed(_defaultSpeed); - await _audioPlayer.setAudioSource(AudioSource.uri(source, tag: item)); + await _audioPlayer.setAudioSource(AudioSource.uri(source)); if (startAt != null && startAt > Duration.zero) { await _audioPlayer.seek(startAt); @@ -233,6 +242,38 @@ class AudioPlayerHandler extends BaseAudioHandler { } } + Future loadCurriculumAudio( + Uri source, { + required String clipId, + required String title, + Duration start = Duration.zero, + Duration? end, + }) async { + final item = MediaItem(id: clipId, title: title, album: 'freeCodeCamp'); + + _codeRadioUrl = null; + _resumeNeedsLiveEdge = false; + _audioConfig = CurriculumAudioConfig(clipId); + + try { + await _audioPlayer.setSpeed(_defaultSpeed); + await _audioPlayer.setAudioSource( + ClippingAudioSource( + start: start, + end: end, + child: AudioSource.uri(source), + ), + ); + + _publish(item); + return true; + } catch (e) { + log('loadCurriculumAudio: Cannot play audio: $e'); + await stop(); + return false; + } + } + MediaItem _toMediaItem(Song song) => MediaItem( id: song.id, title: song.title, @@ -260,7 +301,7 @@ class AudioPlayerHandler extends BaseAudioHandler { systemActions: const {MediaAction.seek}, compactIndices: const [0, 1, 2], ), - CodeRadioAudioConfig() || null => ( + CodeRadioAudioConfig() || CurriculumAudioConfig() || null => ( controls: [playPause, MediaControl.stop], systemActions: const {}, compactIndices: const [0, 1], From 62ebe7ab57f589e7291b930617e614bfacb592aa Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 16 Oct 2026 03:36:37 +0530 Subject: [PATCH 39/52] fix: show assignments in dialogues and scene files restructure --- .../scene_controller.dart} | 4 +- .../scene_controller.freezed.dart} | 2 +- .../scene_controller.g.dart} | 2 +- .../lib/learn/ui/challenge/english_view.dart | 38 +++++++++++++++---- .../lib/learn/ui/challenge/lesson_view.dart | 2 +- .../scene_fullscreen_view.dart | 4 +- .../widgets/scene/scene_canvas.dart | 2 +- .../widgets/scene/scene_controls.dart | 2 +- .../widgets/scene/scene_view.dart | 6 +-- 9 files changed, 42 insertions(+), 20 deletions(-) rename mobile-app-new/lib/learn/{ui/challenge/widgets/scene/scene_viewmodel.dart => controllers/scene_controller.dart} (99%) rename mobile-app-new/lib/learn/{ui/challenge/widgets/scene/scene_viewmodel.freezed.dart => controllers/scene_controller.freezed.dart} (99%) rename mobile-app-new/lib/learn/{ui/challenge/widgets/scene/scene_viewmodel.g.dart => controllers/scene_controller.g.dart} (99%) rename mobile-app-new/lib/learn/ui/{challenge => }/widgets/scene/scene_canvas.dart (98%) rename mobile-app-new/lib/learn/ui/{challenge => }/widgets/scene/scene_controls.dart (97%) rename mobile-app-new/lib/learn/ui/{challenge => }/widgets/scene/scene_view.dart (88%) diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.dart b/mobile-app-new/lib/learn/controllers/scene_controller.dart similarity index 99% rename from mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.dart rename to mobile-app-new/lib/learn/controllers/scene_controller.dart index 298dfd8a8..b853139e6 100644 --- a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.dart +++ b/mobile-app-new/lib/learn/controllers/scene_controller.dart @@ -9,8 +9,8 @@ import 'package:mobile_app_new/learn/services/curriculum_service.dart'; import 'package:mobile_app_new/services/audio_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'scene_viewmodel.freezed.dart'; -part 'scene_viewmodel.g.dart'; +part 'scene_controller.freezed.dart'; +part 'scene_controller.g.dart'; const _cdn = 'https://cdn.freecodecamp.org/curriculum/english/animation-assets/images'; diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.freezed.dart b/mobile-app-new/lib/learn/controllers/scene_controller.freezed.dart similarity index 99% rename from mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.freezed.dart rename to mobile-app-new/lib/learn/controllers/scene_controller.freezed.dart index fd00c4c47..9224fede0 100644 --- a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.freezed.dart +++ b/mobile-app-new/lib/learn/controllers/scene_controller.freezed.dart @@ -3,7 +3,7 @@ // ignore_for_file: type=lint // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark -part of 'scene_viewmodel.dart'; +part of 'scene_controller.dart'; // ************************************************************************** // FreezedGenerator diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.g.dart b/mobile-app-new/lib/learn/controllers/scene_controller.g.dart similarity index 99% rename from mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.g.dart rename to mobile-app-new/lib/learn/controllers/scene_controller.g.dart index 402a30da8..214ae3037 100644 --- a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_viewmodel.g.dart +++ b/mobile-app-new/lib/learn/controllers/scene_controller.g.dart @@ -1,6 +1,6 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'scene_viewmodel.dart'; +part of 'scene_controller.dart'; // ************************************************************************** // RiverpodGenerator diff --git a/mobile-app-new/lib/learn/ui/challenge/english_view.dart b/mobile-app-new/lib/learn/ui/challenge/english_view.dart index 19e97501f..d7d0021e6 100644 --- a/mobile-app-new/lib/learn/ui/challenge/english_view.dart +++ b/mobile-app-new/lib/learn/ui/challenge/english_view.dart @@ -4,14 +4,15 @@ import 'package:html/parser.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/learn/models/challenge_model.dart'; import 'package:mobile_app_new/learn/models/curriculum_model.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/html_card.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/parsed_html.dart'; +import 'package:mobile_app_new/learn/ui/challenge/challenge_viewmodel.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/assignment_tile.dart'; import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_button.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_card.dart'; import 'package:mobile_app_new/learn/ui/challenge/widgets/complete_button.dart'; -import 'package:mobile_app_new/learn/ui/challenge/challenge_viewmodel.dart'; import 'package:mobile_app_new/learn/ui/challenge/widgets/help_dialog.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_view.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/html_card.dart'; +import 'package:mobile_app_new/learn/ui/challenge/widgets/parsed_html.dart'; +import 'package:mobile_app_new/learn/ui/widgets/scene/scene_view.dart'; const _wordStyle = TextStyle(fontSize: 20, letterSpacing: 0); @@ -30,6 +31,10 @@ class EnglishView extends StatefulWidget { class _EnglishViewState extends State { late final _blanks = widget.challenge.fillInTheBlank?.blanks ?? const []; late final _inputs = [for (final _ in _blanks) TextEditingController()]; + late final _assignments = List.filled( + widget.challenge.assignments.length, + false, + ); List? _results; bool get _allCorrect => _results?.every((r) => r) ?? false; @@ -84,6 +89,21 @@ class _EnglishViewState extends State { title: 'Scene', child: SceneView(scene: scene, title: challenge.title), ), + if (challenge.assignments.isNotEmpty) + ChallengeCard( + title: 'Assignments', + child: Column( + children: [ + for (final (i, assignment) in challenge.assignments.indexed) + AssignmentTile( + label: assignment, + value: _assignments[i], + onToggle: () => + setState(() => _assignments[i] = !_assignments[i]), + ), + ], + ), + ), if (challenge.fillInTheBlank case final fill?) ChallengeCard( title: 'Fill in the Blank', @@ -105,6 +125,7 @@ class _EnglishViewState extends State { challenge: challenge, block: widget.block, label: 'Go to Next Challenge', + enabled: _assignments.every((done) => done), ) else ChallengeButton(label: 'Check Answers', onPressed: _check), @@ -158,7 +179,8 @@ class _EnglishViewState extends State { }, ); final painter = TextPainter( - text: TextSpan(text: _blanks[index].answer, style: _wordStyle), + // NOTE: Two extra characters of leeway, so a typo or the cursor isn't clipped. + text: TextSpan(text: '${_blanks[index].answer}mm', style: _wordStyle), textDirection: TextDirection.ltr, )..layout(); final answerWidth = painter.width; @@ -166,7 +188,7 @@ class _EnglishViewState extends State { return Container( margin: const EdgeInsets.symmetric(horizontal: 5), - width: answerWidth + 20, + width: answerWidth, child: TextField( controller: _inputs[index], cursorHeight: 19, @@ -176,7 +198,7 @@ class _EnglishViewState extends State { style: const TextStyle(fontSize: 19, letterSpacing: 0), decoration: InputDecoration( isDense: true, - contentPadding: const EdgeInsets.symmetric(horizontal: 8), + contentPadding: const EdgeInsets.symmetric(horizontal: 4), enabledBorder: border, focusedBorder: border, ), diff --git a/mobile-app-new/lib/learn/ui/challenge/lesson_view.dart b/mobile-app-new/lib/learn/ui/challenge/lesson_view.dart index 0c9551580..03d11f47a 100644 --- a/mobile-app-new/lib/learn/ui/challenge/lesson_view.dart +++ b/mobile-app-new/lib/learn/ui/challenge/lesson_view.dart @@ -11,7 +11,7 @@ import 'package:mobile_app_new/learn/ui/challenge/widgets/challenge_video.dart'; import 'package:mobile_app_new/learn/ui/challenge/challenge_viewmodel.dart'; import 'package:mobile_app_new/learn/ui/challenge/widgets/help_dialog.dart'; import 'package:mobile_app_new/learn/ui/challenge/widgets/questions.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_view.dart'; +import 'package:mobile_app_new/learn/ui/widgets/scene/scene_view.dart'; class LessonView extends StatefulWidget { const LessonView({super.key, required this.challenge, required this.block}); diff --git a/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart b/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart index 822fbaf9b..7975b881c 100644 --- a/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart +++ b/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart @@ -3,8 +3,8 @@ import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/learn/models/challenge_model.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_canvas.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_controls.dart'; +import 'package:mobile_app_new/learn/ui/widgets/scene/scene_canvas.dart'; +import 'package:mobile_app_new/learn/ui/widgets/scene/scene_controls.dart'; class SceneFullscreenView extends StatefulWidget { const SceneFullscreenView({ diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_canvas.dart b/mobile-app-new/lib/learn/ui/widgets/scene/scene_canvas.dart similarity index 98% rename from mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_canvas.dart rename to mobile-app-new/lib/learn/ui/widgets/scene/scene_canvas.dart index d2aeebe1c..ee68e2fa7 100644 --- a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_canvas.dart +++ b/mobile-app-new/lib/learn/ui/widgets/scene/scene_canvas.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/learn/models/challenge_model.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_viewmodel.dart'; +import 'package:mobile_app_new/learn/controllers/scene_controller.dart'; class SceneCanvas extends ConsumerWidget { const SceneCanvas({ diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_controls.dart b/mobile-app-new/lib/learn/ui/widgets/scene/scene_controls.dart similarity index 97% rename from mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_controls.dart rename to mobile-app-new/lib/learn/ui/widgets/scene/scene_controls.dart index 38b48673d..37b78efe4 100644 --- a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_controls.dart +++ b/mobile-app-new/lib/learn/ui/widgets/scene/scene_controls.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/learn/models/challenge_model.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_viewmodel.dart'; +import 'package:mobile_app_new/learn/controllers/scene_controller.dart'; import 'package:mobile_app_new/services/audio_service.dart'; class SceneControls extends ConsumerWidget { diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_view.dart b/mobile-app-new/lib/learn/ui/widgets/scene/scene_view.dart similarity index 88% rename from mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_view.dart rename to mobile-app-new/lib/learn/ui/widgets/scene/scene_view.dart index 9be8400f1..e6ed82128 100644 --- a/mobile-app-new/lib/learn/ui/challenge/widgets/scene/scene_view.dart +++ b/mobile-app-new/lib/learn/ui/widgets/scene/scene_view.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/learn/models/challenge_model.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_canvas.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_controls.dart'; -import 'package:mobile_app_new/learn/ui/challenge/widgets/scene/scene_viewmodel.dart'; +import 'package:mobile_app_new/learn/ui/widgets/scene/scene_canvas.dart'; +import 'package:mobile_app_new/learn/ui/widgets/scene/scene_controls.dart'; +import 'package:mobile_app_new/learn/controllers/scene_controller.dart'; import 'package:mobile_app_new/routing/learn.dart'; class SceneView extends ConsumerStatefulWidget { From 43ea4b32f6720c86a836badf8ee1a70716dceb50 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 16 Oct 2026 04:02:18 +0530 Subject: [PATCH 40/52] chore: remove unnecessary fallbacks and simplify fullscreen scene --- .../learn/controllers/scene_controller.dart | 65 ++++++++----------- .../challenge/widgets/quiz_audio_player.dart | 16 ++--- .../scene_fullscreen_view.dart | 32 ++------- .../learn/ui/widgets/scene/scene_canvas.dart | 25 ++++--- .../learn/ui/widgets/scene/scene_view.dart | 11 ++-- 5 files changed, 56 insertions(+), 93 deletions(-) diff --git a/mobile-app-new/lib/learn/controllers/scene_controller.dart b/mobile-app-new/lib/learn/controllers/scene_controller.dart index b853139e6..5c76b6d35 100644 --- a/mobile-app-new/lib/learn/controllers/scene_controller.dart +++ b/mobile-app-new/lib/learn/controllers/scene_controller.dart @@ -12,8 +12,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'scene_controller.freezed.dart'; part 'scene_controller.g.dart'; -const _cdn = - 'https://cdn.freecodecamp.org/curriculum/english/animation-assets/images'; const _mouthInterval = (min: 85, max: 105); const _blinkInterval = (min: 2000, max: 4000); const _blinkDuration = Duration(milliseconds: 150); @@ -55,27 +53,19 @@ abstract class SceneState with _$SceneState { }) = _SceneState; } -String sceneBackgroundUrl(SceneAssets? assets, String name) { - final file = name.endsWith('.png') ? name : '$name.png'; - return '${assets?.backgrounds ?? '$_cdn/backgrounds'}/$file'; -} +String sceneBackgroundUrl(SceneAssets assets, String name) => + '${assets.backgrounds}/${name.endsWith('.png') ? name : '$name.png'}'; -// NOTE: Falls back to the CDN's folder layout when scene-assets.json hasn't loaded. -CharacterSprites characterSprites(SceneAssets? assets, CharacterState c) { - final a = assets?.characterAssets[c.name]; - String fallback(String file) => - '$_cdn/characters/${c.name.toLowerCase()}/$file'; +CharacterSprites? characterSprites(SceneAssets assets, CharacterState c) { + final a = assets.characterAssets[c.name]; + if (a == null) return null; return ( - base: a?.base ?? fallback('base.png'), - brows: a?.brows ?? fallback('brows.png'), - eyes: c.eyesClosed - ? a?.eyesClosed ?? fallback('eyes-closed.png') - : a?.eyesOpen ?? fallback('eyes-open.png'), - glasses: a?.glasses, - mouth: c.mouthOpen - ? a?.mouthOpen ?? fallback('mouth-open.png') - : a?.mouthClosed ?? fallback('mouth-closed.png'), + base: a.base, + brows: a.brows, + eyes: c.eyesClosed ? a.eyesClosed : a.eyesOpen, + glasses: a.glasses, + mouth: c.mouthOpen ? a.mouthOpen : a.mouthClosed, ); } @@ -144,17 +134,10 @@ class SceneNotifier extends _$SceneNotifier { _completed = false; _applied.clear(); - var next = _initial().copyWith( - showCaptions: state.showCaptions, - isActive: true, + state = _applyDue( + _initial().copyWith(showCaptions: state.showCaptions, isActive: true), + (start) => start < _audioOffset, ); - for (final (i, command) in scene.commands.indexed) { - if (command.startTime < _audioOffset) { - _applied.add(i); - next = _apply(next, command); - } - } - state = next; await Future.delayed( Duration(milliseconds: (_audioOffset * 1000).round()), @@ -206,22 +189,18 @@ class SceneNotifier extends _$SceneNotifier { void _complete() { _completed = true; - var next = state.copyWith(line: null); - for (final (i, command) in scene.commands.indexed) { - if (_applied.add(i)) next = _apply(next, command); - } - state = next; + state = _applyDue(state.copyWith(line: null), (_) => true); } void _onPosition(Duration position) { if (!_isActive || !_playing || _completed) return; final seconds = position.inMilliseconds / 1000 + _audioOffset; - var next = state; + var next = _applyDue(state, (start) => seconds >= start); final speaking = {}; SceneLine? active; - for (final (i, command) in scene.commands.indexed) { + for (final command in scene.commands) { final start = command.startTime.toDouble(); final finish = command.finishTime?.toDouble(); @@ -235,8 +214,6 @@ class SceneNotifier extends _$SceneNotifier { ); } } - - if (seconds >= start && _applied.add(i)) next = _apply(next, command); } if (active != null && active != next.line) { @@ -255,6 +232,16 @@ class SceneNotifier extends _$SceneNotifier { } } + SceneState _applyDue(SceneState current, bool Function(num start) due) { + var next = current; + for (final (i, command) in scene.commands.indexed) { + if (due(command.startTime) && _applied.add(i)) { + next = _apply(next, command); + } + } + return next; + } + SceneState _apply(SceneState current, SceneCommand command) { final characters = [...current.characters]; final index = characters.indexWhere((c) => c.name == command.character); diff --git a/mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart b/mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart index 7efee1edf..41e00cfd8 100644 --- a/mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart +++ b/mobile-app-new/lib/learn/ui/challenge/widgets/quiz_audio_player.dart @@ -57,15 +57,13 @@ class _QuizAudioPlayerState extends ConsumerState { ); } - final handler = _handler; - return Column( children: [ StreamBuilder( - stream: handler.playbackState, + stream: _handler.playbackState, builder: (context, snapshot) { final state = snapshot.data ?? PlaybackState(); - final isActive = handler.curriculumClipId == _clip.id; + final isActive = _handler.curriculumClipId == _clip.id; final completed = state.processingState == AudioProcessingState.completed; final playing = isActive && state.playing && !completed; @@ -73,10 +71,10 @@ class _QuizAudioPlayerState extends ConsumerState { return Column( children: [ StreamBuilder( - stream: handler.positionStream, + stream: _handler.positionStream, builder: (context, position) => _Progress( position: isActive ? position.data : null, - total: isActive ? handler.duration : null, + total: isActive ? _handler.duration : null, ), ), if (_loading) @@ -93,9 +91,9 @@ class _QuizAudioPlayerState extends ConsumerState { icon: Icon(playing ? Icons.pause : Icons.play_arrow), onPressed: () async { if (!isActive) return _loadAndPlay(); - if (playing) return handler.pause(); - if (completed) await handler.seek(Duration.zero); - await handler.play(); + if (playing) return _handler.pause(); + if (completed) await _handler.seek(Duration.zero); + await _handler.play(); }, ), ], diff --git a/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart b/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart index 7975b881c..9e58bbc93 100644 --- a/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart +++ b/mobile-app-new/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart @@ -21,9 +21,6 @@ class SceneFullscreenView extends StatefulWidget { } class _SceneFullscreenViewState extends State { - final _controlsKey = GlobalKey(); - double _controlsHeight = 0; - @override void initState() { super.initState(); @@ -32,12 +29,6 @@ class _SceneFullscreenViewState extends State { DeviceOrientation.landscapeRight, ]); SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); - WidgetsBinding.instance.addPostFrameCallback((_) { - final box = _controlsKey.currentContext?.findRenderObject() as RenderBox?; - if (mounted && box != null && box.hasSize) { - setState(() => _controlsHeight = box.size.height); - } - }); } @override @@ -51,14 +42,16 @@ class _SceneFullscreenViewState extends State { Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, + extendBody: true, + bottomNavigationBar: SceneControls( + scene: widget.scene, + title: widget.title, + fullscreen: true, + ), body: Stack( children: [ Positioned.fill( - child: SceneCanvas( - scene: widget.scene, - title: widget.title, - captionsBottom: _controlsHeight, - ), + child: SceneCanvas(scene: widget.scene, title: widget.title), ), Positioned( top: 16, @@ -79,17 +72,6 @@ class _SceneFullscreenViewState extends State { ), ), ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: SceneControls( - key: _controlsKey, - scene: widget.scene, - title: widget.title, - fullscreen: true, - ), - ), ], ), ); diff --git a/mobile-app-new/lib/learn/ui/widgets/scene/scene_canvas.dart b/mobile-app-new/lib/learn/ui/widgets/scene/scene_canvas.dart index ee68e2fa7..6ef3adbe2 100644 --- a/mobile-app-new/lib/learn/ui/widgets/scene/scene_canvas.dart +++ b/mobile-app-new/lib/learn/ui/widgets/scene/scene_canvas.dart @@ -6,21 +6,17 @@ import 'package:mobile_app_new/learn/models/challenge_model.dart'; import 'package:mobile_app_new/learn/controllers/scene_controller.dart'; class SceneCanvas extends ConsumerWidget { - const SceneCanvas({ - super.key, - required this.scene, - required this.title, - this.captionsBottom = 0, - }); + const SceneCanvas({super.key, required this.scene, required this.title}); final Scene scene; final String title; - final double captionsBottom; @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(sceneProvider(scene, title)); final assets = ref.watch(sceneAssetsProvider).value; + if (assets == null) return const ColoredBox(color: FccColors.gray75); + final line = state.line; // NOTE: Lower z is further back, so it paints first. final characters = [...state.characters] @@ -42,17 +38,18 @@ class SceneCanvas extends ConsumerWidget { ), ), for (final character in characters) - _Character( - key: ValueKey(character.name), - state: character, - sprites: characterSprites(assets, character), - canvas: constraints.biggest, - ), + if (characterSprites(assets, character) case final sprites?) + _Character( + key: ValueKey(character.name), + state: character, + sprites: sprites, + canvas: constraints.biggest, + ), if (state.showCaptions && line != null) Positioned( left: 0, right: 0, - bottom: captionsBottom, + bottom: MediaQuery.paddingOf(context).bottom, child: _Caption(line: line), ), ], diff --git a/mobile-app-new/lib/learn/ui/widgets/scene/scene_view.dart b/mobile-app-new/lib/learn/ui/widgets/scene/scene_view.dart index e6ed82128..f0592f170 100644 --- a/mobile-app-new/lib/learn/ui/widgets/scene/scene_view.dart +++ b/mobile-app-new/lib/learn/ui/widgets/scene/scene_view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_app_new/fcc_theme.dart'; import 'package:mobile_app_new/learn/models/challenge_model.dart'; +import 'package:mobile_app_new/learn/models/scene_assets_model.dart'; import 'package:mobile_app_new/learn/ui/widgets/scene/scene_canvas.dart'; import 'package:mobile_app_new/learn/ui/widgets/scene/scene_controls.dart'; import 'package:mobile_app_new/learn/controllers/scene_controller.dart'; @@ -21,13 +22,8 @@ class SceneView extends ConsumerStatefulWidget { class _SceneViewState extends ConsumerState { bool _precached = false; - @override - void didChangeDependencies() { - super.didChangeDependencies(); - if (_precached) return; + void _precache(SceneAssets assets) { _precached = true; - - final assets = ref.read(sceneAssetsProvider).value; final backgrounds = { widget.scene.setup.background, for (final c in widget.scene.commands) ?c.background, @@ -42,6 +38,9 @@ class _SceneViewState extends ConsumerState { @override Widget build(BuildContext context) { + final assets = ref.watch(sceneAssetsProvider).value; + if (assets != null && !_precached) _precache(assets); + return Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), child: Column( From d70de32808a3e05c976b1c55346e98cdb5b8df29 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Sun, 18 Oct 2026 15:51:08 +0530 Subject: [PATCH 41/52] =?UTF-8?q?Learn=204/4=20=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/app/src/main/AndroidManifest.xml | 4 +- .../main/res/xml/network_security_config.xml | 6 + mobile-app-new/assets/fonts/Hack-Regular.ttf | Bin 0 -> 305172 bytes mobile-app-new/assets/fonts/Lato-Regular.ttf | Bin 0 -> 120196 bytes mobile-app-new/assets/test_runner/VERSION.txt | 1 + .../assets/test_runner/babel/babel.min.js | 2 + .../dist/126.217b2b5c2726a434eadb.js | 1 + .../dist/214.992f651a9fe4ee5dc553.js | 1 + .../dist/26.d4b48aa36b1383e317ab.js | 2 + .../26.d4b48aa36b1383e317ab.js.LICENSE.txt | 68 ++ .../dist/302.c8ab63712f1603f9d24f.js | 1 + .../dist/325.090d1ef9d7ecde370904.js | 6 + .../dist/325.713e5e15bda3843ab320.js | 6 + .../dist/361.dca39c129f300a617e4f.js | 2 + .../361.dca39c129f300a617e4f.js.LICENSE.txt | 10 + .../dist/512.963dc1e3a5deb572ac05.js | 1 + .../dist/670.0a71e5e6c744ab2b7403.js | 1 + .../dist/697.c1141ab556afb9c3f94b.js | 1 + .../dist/821.b35c74c0050a22ec9938.js | 1 + .../dist/836.487cb77f05dbe6f3a434.js | 1 + .../dist/943.99d1c181a2263104e4d0.js | 1 + .../test_runner/dist/dom-test-evaluator.js | 2 + .../dist/dom-test-evaluator.js.LICENSE.txt | 550 ++++++++++++++++ .../enzyme-adapter.a8f1009fa72e3f86807e.js | 1 + .../dist/enzyme.a8f1009fa72e3f86807e.js | 1 + .../dist/explorer.45ddb7897528f130cbed.js | 1 + .../dist/explorer.59c927c94d2bdb7bd844.js | 1 + .../dist/explorer.767baf5fecabfbb23cf8.js | 1 + .../dist/explorer.7858a111e0df509ae645.js | 1 + .../assets/test_runner/dist/index.js | 1 + .../dist/javascript-test-evaluator.js | 2 + .../javascript-test-evaluator.js.LICENSE.txt | 539 +++++++++++++++ .../test_runner/dist/python-test-evaluator.js | 2 + .../dist/python-test-evaluator.js.LICENSE.txt | 539 +++++++++++++++ .../integration_test/learn/landing_test.dart | 74 +++ .../test_runner/curriculum_test.dart | 145 ++++ mobile-app-new/integration_test_runner.dart | 230 +++++++ .../xcshareddata/swiftpm/Package.resolved | 9 + .../xcshareddata/swiftpm/Package.resolved | 9 + .../learn/controllers/scene_controller.g.dart | 2 +- .../learn/models/challenge_file_model.dart | 4 + .../models/challenge_file_model.freezed.dart | 4 +- .../lib/learn/models/challenge_model.dart | 26 + .../learn/models/challenge_model.freezed.dart | 618 +++++++++++++++++- .../lib/learn/models/challenge_model.g.dart | 37 ++ .../learn/models/daily_challenge_model.dart | 38 ++ .../models/daily_challenge_model.freezed.dart | 595 +++++++++++++++++ .../learn/models/daily_challenge_model.g.dart | 48 ++ .../daily_challenge_repository.dart | 32 +- .../daily_challenge_repository.g.dart | 2 +- .../learn/repositories/prefs_repository.dart | 18 + .../repositories/prefs_repository.g.dart | 58 ++ .../lib/learn/services/code_service.dart | 165 +++++ .../lib/learn/services/code_service.g.dart | 56 ++ .../learn/services/curriculum_service.dart | 14 +- .../daily_challenge_reminder_service.dart | 86 +++ .../daily_challenge_reminder_service.g.dart | 62 ++ .../services/daily_challenge_service.dart | 60 +- .../services/daily_challenge_service.g.dart | 2 +- .../learn/services/test_runner_service.dart | 254 +++++++ .../learn/services/test_runner_service.g.dart | 123 ++++ .../challenge/challenge_placeholder_view.dart | 20 - .../learn/ui/challenge/challenge_view.dart | 103 ++- .../ui/challenge/challenge_viewmodel.dart | 29 +- .../ui/challenge/challenge_viewmodel.g.dart | 2 +- .../lib/learn/ui/challenge/english_view.dart | 3 +- .../lib/learn/ui/challenge/lesson_view.dart | 2 +- .../ui/challenge/python_project_view.dart | 3 +- .../ui/challenge/widgets/complete_button.dart | 13 +- .../ui/challenge/widgets/help_dialog.dart | 22 +- .../daily_challenge/daily_challenge_view.dart | 86 +++ .../daily_challenge_viewmodel.dart | 21 + .../daily_challenge_viewmodel.g.dart | 141 ++++ .../ui/editor_challenge/editor_body.dart | 112 ++++ .../editor_challenge_view.dart | 506 ++++++++++++++ .../editor_challenge_viewmodel.dart | 186 ++++++ .../editor_challenge_viewmodel.freezed.dart | 336 ++++++++++ .../editor_challenge_viewmodel.g.dart | 151 +++++ .../ui/editor_challenge/editor_panels.dart | 221 +++++++ mobile-app-new/lib/main.dart | 9 +- .../repositories/progress_repository.dart | 19 +- mobile-app-new/lib/routing/learn.dart | 5 +- .../lib/services/notification_service.dart | 36 + mobile-app-new/pubspec.lock | 72 ++ mobile-app-new/pubspec.yaml | 10 + .../test_driver/integration_test.dart | 1 + 86 files changed, 6483 insertions(+), 154 deletions(-) create mode 100644 mobile-app-new/android/app/src/main/res/xml/network_security_config.xml create mode 100644 mobile-app-new/assets/fonts/Hack-Regular.ttf create mode 100644 mobile-app-new/assets/fonts/Lato-Regular.ttf create mode 100644 mobile-app-new/assets/test_runner/VERSION.txt create mode 100644 mobile-app-new/assets/test_runner/babel/babel.min.js create mode 100644 mobile-app-new/assets/test_runner/dist/126.217b2b5c2726a434eadb.js create mode 100644 mobile-app-new/assets/test_runner/dist/214.992f651a9fe4ee5dc553.js create mode 100644 mobile-app-new/assets/test_runner/dist/26.d4b48aa36b1383e317ab.js create mode 100644 mobile-app-new/assets/test_runner/dist/26.d4b48aa36b1383e317ab.js.LICENSE.txt create mode 100644 mobile-app-new/assets/test_runner/dist/302.c8ab63712f1603f9d24f.js create mode 100644 mobile-app-new/assets/test_runner/dist/325.090d1ef9d7ecde370904.js create mode 100644 mobile-app-new/assets/test_runner/dist/325.713e5e15bda3843ab320.js create mode 100644 mobile-app-new/assets/test_runner/dist/361.dca39c129f300a617e4f.js create mode 100644 mobile-app-new/assets/test_runner/dist/361.dca39c129f300a617e4f.js.LICENSE.txt create mode 100644 mobile-app-new/assets/test_runner/dist/512.963dc1e3a5deb572ac05.js create mode 100644 mobile-app-new/assets/test_runner/dist/670.0a71e5e6c744ab2b7403.js create mode 100644 mobile-app-new/assets/test_runner/dist/697.c1141ab556afb9c3f94b.js create mode 100644 mobile-app-new/assets/test_runner/dist/821.b35c74c0050a22ec9938.js create mode 100644 mobile-app-new/assets/test_runner/dist/836.487cb77f05dbe6f3a434.js create mode 100644 mobile-app-new/assets/test_runner/dist/943.99d1c181a2263104e4d0.js create mode 100644 mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js create mode 100644 mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt create mode 100644 mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js create mode 100644 mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js create mode 100644 mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js create mode 100644 mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js create mode 100644 mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js create mode 100644 mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js create mode 100644 mobile-app-new/assets/test_runner/dist/index.js create mode 100644 mobile-app-new/assets/test_runner/dist/javascript-test-evaluator.js create mode 100644 mobile-app-new/assets/test_runner/dist/javascript-test-evaluator.js.LICENSE.txt create mode 100644 mobile-app-new/assets/test_runner/dist/python-test-evaluator.js create mode 100644 mobile-app-new/assets/test_runner/dist/python-test-evaluator.js.LICENSE.txt create mode 100644 mobile-app-new/integration_test/learn/landing_test.dart create mode 100644 mobile-app-new/integration_test/test_runner/curriculum_test.dart create mode 100644 mobile-app-new/integration_test_runner.dart create mode 100644 mobile-app-new/lib/learn/repositories/prefs_repository.dart create mode 100644 mobile-app-new/lib/learn/repositories/prefs_repository.g.dart create mode 100644 mobile-app-new/lib/learn/services/code_service.dart create mode 100644 mobile-app-new/lib/learn/services/code_service.g.dart create mode 100644 mobile-app-new/lib/learn/services/daily_challenge_reminder_service.dart create mode 100644 mobile-app-new/lib/learn/services/daily_challenge_reminder_service.g.dart create mode 100644 mobile-app-new/lib/learn/services/test_runner_service.dart create mode 100644 mobile-app-new/lib/learn/services/test_runner_service.g.dart delete mode 100644 mobile-app-new/lib/learn/ui/challenge/challenge_placeholder_view.dart create mode 100644 mobile-app-new/lib/learn/ui/daily_challenge/daily_challenge_view.dart create mode 100644 mobile-app-new/lib/learn/ui/daily_challenge/daily_challenge_viewmodel.dart create mode 100644 mobile-app-new/lib/learn/ui/daily_challenge/daily_challenge_viewmodel.g.dart create mode 100644 mobile-app-new/lib/learn/ui/editor_challenge/editor_body.dart create mode 100644 mobile-app-new/lib/learn/ui/editor_challenge/editor_challenge_view.dart create mode 100644 mobile-app-new/lib/learn/ui/editor_challenge/editor_challenge_viewmodel.dart create mode 100644 mobile-app-new/lib/learn/ui/editor_challenge/editor_challenge_viewmodel.freezed.dart create mode 100644 mobile-app-new/lib/learn/ui/editor_challenge/editor_challenge_viewmodel.g.dart create mode 100644 mobile-app-new/lib/learn/ui/editor_challenge/editor_panels.dart diff --git a/mobile-app-new/android/app/src/main/AndroidManifest.xml b/mobile-app-new/android/app/src/main/AndroidManifest.xml index f7ec8a7ed..3a6ba0ed2 100644 --- a/mobile-app-new/android/app/src/main/AndroidManifest.xml +++ b/mobile-app-new/android/app/src/main/AndroidManifest.xml @@ -4,10 +4,12 @@ + + android:icon="@mipmap/ic_launcher" + android:networkSecurityConfig="@xml/network_security_config"> + + + localhost + + diff --git a/mobile-app-new/assets/fonts/Hack-Regular.ttf b/mobile-app-new/assets/fonts/Hack-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..75a253b4740b671a2d473b44505e9e4a82b13715 GIT binary patch literal 305172 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

Af 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 IlF|780KVYecK`qY literal 0 HcmV?d00001 diff --git a/mobile-app-new/assets/fonts/Lato-Regular.ttf b/mobile-app-new/assets/fonts/Lato-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..04ea8efb1367727b081dea87e63818be0a4d02f0 GIT binary patch literal 120196 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�r(aJ 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 d3)lz{Xfc9M412p literal 0 HcmV?d00001 diff --git a/mobile-app-new/assets/test_runner/VERSION.txt b/mobile-app-new/assets/test_runner/VERSION.txt new file mode 100644 index 000000000..f7ee06693 --- /dev/null +++ b/mobile-app-new/assets/test_runner/VERSION.txt @@ -0,0 +1 @@ +9.0.0 diff --git a/mobile-app-new/assets/test_runner/babel/babel.min.js b/mobile-app-new/assets/test_runner/babel/babel.min.js new file mode 100644 index 000000000..795374996 --- /dev/null +++ b/mobile-app-new/assets/test_runner/babel/babel.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Babel={})}(this,(function(e){"use strict";function t(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(r){if("default"!==r&&!(r in e)){var a=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,a.get?a:{enumerable:!0,get:function(){return t[r]}})}}))})),Object.freeze(e)}var r=Object.freeze({__proto__:null,get DEFAULT_EXTENSIONS(){return FU},get File(){return dD},get buildExternalHelpers(){return OD},get createConfigItem(){return BF},get createConfigItemAsync(){return OF},get createConfigItemSync(){return NF},get getEnv(){return YD},get loadOptions(){return IF},get loadOptionsAsync(){return CF},get loadOptionsSync(){return _F},get loadPartialConfig(){return PF},get loadPartialConfigAsync(){return SF},get loadPartialConfigSync(){return TF},get parse(){return NU},get parseAsync(){return MU},get parseSync(){return BU},get resolvePlugin(){return KD},get resolvePreset(){return zD},get template(){return tS},get tokTypes(){return vE},get transform(){return wU},get transformAsync(){return TU},get transformFile(){return PU},get transformFileAsync(){return kU},get transformFileSync(){return AU},get transformFromAst(){return _U},get transformFromAstAsync(){return DU},get transformFromAstSync(){return IU},get transformSync(){return SU},get traverse(){return IP},get types(){return Pu},get version(){return LU}});function a(){a=function(){return t};var e,t={},r=Object.prototype,n=r.hasOwnProperty,s=Object.defineProperty||function(e,t,r){e[t]=r.value},i="function"==typeof Symbol?Symbol:{},o=i.iterator||"@@iterator",d=i.asyncIterator||"@@asyncIterator",c=i.toStringTag||"@@toStringTag";function l(e,t,r){return Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{l({},"")}catch(e){l=function(e,t,r){return e[t]=r}}function u(e,t,r,a){var n=t&&t.prototype instanceof v?t:v,i=Object.create(n.prototype),o=new _(a||[]);return s(i,"_invoke",{value:P(e,r,o)}),i}function p(e,t,r){try{return{type:"normal",arg:e.call(t,r)}}catch(e){return{type:"throw",arg:e}}}t.wrap=u;var f="suspendedStart",g="suspendedYield",y="executing",m="completed",h={};function v(){}function b(){}function R(){}var x={};l(x,o,(function(){return this}));var j=Object.getPrototypeOf,E=j&&j(j(I([])));E&&E!==r&&n.call(E,o)&&(x=E);var w=R.prototype=v.prototype=Object.create(x);function S(e){["next","throw","return"].forEach((function(t){l(e,t,(function(e){return this._invoke(t,e)}))}))}function T(e,t){function r(a,s,i,o){var d=p(e[a],e,s);if("throw"!==d.type){var c=d.arg,l=c.value;return l&&"object"==typeof l&&n.call(l,"__await")?t.resolve(l.__await).then((function(e){r("next",e,i,o)}),(function(e){r("throw",e,i,o)})):t.resolve(l).then((function(e){c.value=e,i(c)}),(function(e){return r("throw",e,i,o)}))}o(d.arg)}var a;s(this,"_invoke",{value:function(e,n){function s(){return new t((function(t,a){r(e,n,t,a)}))}return a=a?a.then(s,s):s()}})}function P(t,r,a){var n=f;return function(s,i){if(n===y)throw new Error("Generator is already running");if(n===m){if("throw"===s)throw i;return{value:e,done:!0}}for(a.method=s,a.arg=i;;){var o=a.delegate;if(o){var d=A(o,a);if(d){if(d===h)continue;return d}}if("next"===a.method)a.sent=a._sent=a.arg;else if("throw"===a.method){if(n===f)throw n=m,a.arg;a.dispatchException(a.arg)}else"return"===a.method&&a.abrupt("return",a.arg);n=y;var c=p(t,r,a);if("normal"===c.type){if(n=a.done?m:g,c.arg===h)continue;return{value:c.arg,done:a.done}}"throw"===c.type&&(n=m,a.method="throw",a.arg=c.arg)}}}function A(t,r){var a=r.method,n=t.iterator[a];if(n===e)return r.delegate=null,"throw"===a&&t.iterator.return&&(r.method="return",r.arg=e,A(t,r),"throw"===r.method)||"return"!==a&&(r.method="throw",r.arg=new TypeError("The iterator does not provide a '"+a+"' method")),h;var s=p(n,t.iterator,r.arg);if("throw"===s.type)return r.method="throw",r.arg=s.arg,r.delegate=null,h;var i=s.arg;return i?i.done?(r[t.resultName]=i.value,r.next=t.nextLoc,"return"!==r.method&&(r.method="next",r.arg=e),r.delegate=null,h):i:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,h)}function k(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function C(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function _(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(k,this),this.reset(!0)}function I(t){if(t||""===t){var r=t[o];if(r)return r.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var a=-1,s=function r(){for(;++a=0;--s){var i=this.tryEntries[s],o=i.completion;if("root"===i.tryLoc)return a("end");if(i.tryLoc<=this.prev){var d=n.call(i,"catchLoc"),c=n.call(i,"finallyLoc");if(d&&c){if(this.prev=0;--r){var a=this.tryEntries[r];if(a.tryLoc<=this.prev&&n.call(a,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),C(r),h}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var a=r.completion;if("throw"===a.type){var n=a.arg;C(r)}return n}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,a){return this.delegate={iterator:I(t),resultName:r,nextLoc:a},"next"===this.method&&(this.arg=e),h}},t}function n(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var a=r.call(e,t||"default");if("object"!=typeof a)return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}function s(e,t,r,a,n,s,i){try{var o=e[s](i),d=o.value}catch(e){return void r(e)}o.done?t(d):Promise.resolve(d).then(a,n)}function i(e){return function(){var t=this,r=arguments;return new Promise((function(a,n){var i=e.apply(t,r);function o(e){s(i,a,n,o,d,"next",e)}function d(e){s(i,a,n,o,d,"throw",e)}o(void 0)}))}}function o(e,t){for(var r=0;r=0||(n[r]=e[r]);return n}function y(e,t){if(null==e)return{};var r,a,n=g(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function m(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function h(e,t){return t||(t=e.slice(0)),e.raw=t,e}function v(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var a,n,s,i,o=[],d=!0,c=!1;try{if(s=(r=r.call(e)).next,0===t){if(Object(r)!==r)return;d=!1}else for(;!(d=(a=s.call(r)).done)&&(o.push(a.value),o.length!==t);d=!0);}catch(e){c=!0,n=e}finally{try{if(!d&&null!=r.return&&(i=r.return(),Object(i)!==i))return}finally{if(c)throw n}}return o}}(e,t)||R(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function b(e){return function(e){if(Array.isArray(e))return x(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||R(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function R(e,t){if(e){if("string"==typeof e)return x(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?x(e,t):void 0}}function x(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,a=new Array(t);r=e.length?{done:!0}:{done:!1,value:e[a++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var E=Object.freeze({__proto__:null,BindingIdentifier:["Identifier"],BlockScoped:null,ExistentialTypeParam:["ExistsTypeAnnotation"],Expression:["Expression"],Flow:["Flow","ImportDeclaration","ExportDeclaration","ImportSpecifier"],ForAwaitStatement:["ForOfStatement"],Generated:null,NumericLiteralTypeAnnotation:["NumberLiteralTypeAnnotation"],Pure:null,Referenced:null,ReferencedIdentifier:["Identifier","JSXIdentifier"],ReferencedMemberExpression:["MemberExpression"],RestProperty:["RestElement"],Scope:["Scopable","Pattern"],SpreadProperty:["RestElement"],Statement:["Statement"],User:null,Var:["VariableDeclaration"]});function w(e,t){for(var r=0,a=Object.keys(t);rn.length)return!1;for(var i=0,o=s.length-1;i1)for(var r=1;re)return!1;if((r+=t[a+1])>=e)return!0}return!1}function qr(e){return e<65?36===e:e<=90||(e<97?95===e:e<=122||(e<=65535?e>=170&&Br.test(String.fromCharCode(e)):Ur(e,Lr)))}function Wr(e){return e<48?36===e:e<58||!(e<65)&&(e<=90||(e<97?95===e:e<=122||(e<=65535?e>=170&&Mr.test(String.fromCharCode(e)):Ur(e,Lr)||Ur(e,Fr))))}function Gr(e){for(var t=!0,r=0;r=48&&e<=57},ra={decBinOct:new Set([46,66,69,79,95,98,101,111]),hex:new Set([46,88,95,120])},aa={bin:function(e){return 48===e||49===e},oct:function(e){return e>=48&&e<=55},dec:function(e){return e>=48&&e<=57},hex:function(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}};function na(e,t,r,a,n,s){for(var i=r,o=a,d=n,c="",l=null,u=r,p=t.length;;){if(r>=p){s.unterminated(i,o,d),c+=t.slice(u,r);break}var f=t.charCodeAt(r);if(sa(e,f,t,r)){c+=t.slice(u,r);break}if(92===f){c+=t.slice(u,r);var g=ia(t,r,a,n,"template"===e,s);null!==g.ch||l?c+=g.ch:l={pos:r,lineStart:a,curLine:n},r=g.pos,a=g.lineStart,n=g.curLine,u=r}else 8232===f||8233===f?(++n,a=++r):10===f||13===f?"template"===e?(c+=t.slice(u,r)+"\n",++r,13===f&&10===t.charCodeAt(r)&&++r,++n,u=a=r):s.unterminated(i,o,d):++r}return{pos:r,str:c,firstInvalidLoc:l,lineStart:a,curLine:n,containsInvalid:!!l}}function sa(e,t,r,a){return"template"===e?96===t||36===t&&123===r.charCodeAt(a+1):t===("double"===e?34:39)}function ia(e,t,r,a,n,s){var i=!n;t++;var o=function(e){return{pos:t,ch:e,lineStart:r,curLine:a}},d=e.charCodeAt(t++);switch(d){case 110:return o("\n");case 114:return o("\r");case 120:var c,l=oa(e,t,r,a,2,!1,i,s);return c=l.code,t=l.pos,o(null===c?null:String.fromCharCode(c));case 117:var u,p=ca(e,t,r,a,i,s);return u=p.code,t=p.pos,o(null===u?null:String.fromCodePoint(u));case 116:return o("\t");case 98:return o("\b");case 118:return o("\v");case 102:return o("\f");case 13:10===e.charCodeAt(t)&&++t;case 10:r=t,++a;case 8232:case 8233:return o("");case 56:case 57:if(n)return o(null);s.strictNumericEscape(t-1,r,a);default:if(d>=48&&d<=55){var f=t-1,g=e.slice(f,t+2).match(/^[0-7]+/)[0],y=parseInt(g,8);y>255&&(g=g.slice(0,-1),y=parseInt(g,8)),t+=g.length-1;var m=e.charCodeAt(t);if("0"!==g||56===m||57===m){if(n)return o(null);s.strictNumericEscape(f,r,a)}return o(String.fromCharCode(y))}return o(String.fromCharCode(d))}}function oa(e,t,r,a,n,s,i,o){var d,c=t,l=da(e,t,r,a,16,n,s,!1,o,!i);return d=l.n,t=l.pos,null===d&&(i?o.invalidEscapeSequence(c,r,a):t=c-1),{code:d,pos:t}}function da(e,t,r,a,n,s,i,o,d,c){for(var l=t,u=16===n?ra.hex:ra.decBinOct,p=16===n?aa.hex:10===n?aa.dec:8===n?aa.oct:aa.bin,f=!1,g=0,y=0,m=null==s?1/0:s;y=97?h-97+10:h>=65?h-65+10:ta(h)?h-48:1/0)>=n){if(v<=9&&c)return{n:null,pos:t};if(v<=9&&d.invalidDigit(t,r,a,n))v=0;else{if(!i)break;v=0,f=!0}}++t,g=g*n+v}else{var b=e.charCodeAt(t-1),R=e.charCodeAt(t+1);if(o){if(Number.isNaN(R)||!p(R)||u.has(b)||u.has(R)){if(c)return{n:null,pos:t};d.unexpectedNumericSeparator(t,r,a)}}else{if(c)return{n:null,pos:t};d.numericSeparatorInEscapeSequence(t,r,a)}++t}}return t===l||null!=s&&t-l!==s||f?{n:null,pos:t}:{n:g,pos:t}}function ca(e,t,r,a,n,s){var i;if(123===e.charCodeAt(t)){var o=oa(e,++t,r,a,e.indexOf("}",t)-t,!0,n,s);if(i=o.code,t=o.pos,++t,null!==i&&i>1114111){if(!n)return{code:null,pos:t};s.invalidCodePoint(t,r,a)}}else{var d=oa(e,t,r,a,4,!1,n,s);i=d.code,t=d.pos}return{code:i,pos:t}}var la=["consequent","body","alternate"],ua=["leadingComments","trailingComments","innerComments"],pa=["||","&&","??"],fa=["++","--"],ga=[">","<",">=","<="],ya=["==","===","!=","!=="],ma=[].concat(ya,["in","instanceof"]),ha=[].concat(b(ma),ga),va=["-","/","%","*","**","&","|",">>",">>>","<<","^"],ba=["+"].concat(va,b(ha),["|>"]),Ra=["=","+="].concat(b(va.map((function(e){return e+"="}))),b(pa.map((function(e){return e+"="})))),xa=["delete","!"],ja=["+","-","~"],Ea=["typeof"],wa=["void","throw"].concat(xa,ja,Ea),Sa={optional:["typeAnnotation","typeParameters","returnType"],force:["start","loc","end"]},Ta=Symbol.for("var used to be block scoped"),Pa=Symbol.for("should not be considered a local binding"),Aa={},ka={},Ca={},_a={},Ia={},Da={},Oa={};function Na(e){return Array.isArray(e)?"array":null===e?"null":typeof e}function Ba(e){return{validate:e}}function Ma(e){return"string"==typeof e?Ha(e):Ha.apply(void 0,b(e))}function La(e){return Ba(Ma(e))}function Fa(e){return{validate:e,optional:!0}}function Ua(e){return{validate:Ma(e),optional:!0}}function qa(e){return t=Ma(e),Ja(za("array"),Ga(t));var t}function Wa(e){return Ba(qa(e))}function Ga(e){function t(t,r,a){if(Array.isArray(a))for(var n=0;n=2&&"type"in t[0]&&"array"===t[0].type&&!("each"in t[1]))throw new Error('An assertValueType("array") validator can only be followed by an assertEach(...) validator.');return a}var Ya=["aliases","builder","deprecatedAlias","fields","inherits","visitor","validate"],$a=["default","optional","deprecated","validate"],Qa={};function Za(){for(var e=arguments.length,t=new Array(e),r=0;r0:d&&"object"==typeof d)throw new Error("field defaults can only be primitives or empty arrays currently");a[i]={default:Array.isArray(d)?[]:d,optional:o.optional,deprecated:o.deprecated,validate:o.validate}}for(var c=t.visitor||r.visitor||[],l=t.aliases||r.aliases||[],u=t.builder||r.builder||t.visitor||[],p=0,f=Object.keys(t);p+s+1)throw new TypeError("RestElement must be last element of "+n)}}}),tn("ReturnStatement",{visitor:["argument"],aliases:["Statement","Terminatorless","CompletionStatement"],fields:{argument:{validate:Ha("Expression"),optional:!0}}}),tn("SequenceExpression",{visitor:["expressions"],fields:{expressions:{validate:Ja(za("array"),Ga(Ha("Expression")))}},aliases:["Expression"]}),tn("ParenthesizedExpression",{visitor:["expression"],aliases:["Expression","ExpressionWrapper"],fields:{expression:{validate:Ha("Expression")}}}),tn("SwitchCase",{visitor:["test","consequent"],fields:{test:{validate:Ha("Expression"),optional:!0},consequent:{validate:Ja(za("array"),Ga(Ha("Statement")))}}}),tn("SwitchStatement",{visitor:["discriminant","cases"],aliases:["Statement","BlockParent","Scopable"],fields:{discriminant:{validate:Ha("Expression")},cases:{validate:Ja(za("array"),Ga(Ha("SwitchCase")))}}}),tn("ThisExpression",{aliases:["Expression"]}),tn("ThrowStatement",{visitor:["argument"],aliases:["Statement","Terminatorless","CompletionStatement"],fields:{argument:{validate:Ha("Expression")}}}),tn("TryStatement",{visitor:["block","handler","finalizer"],aliases:["Statement"],fields:{block:{validate:Ja(Ha("BlockStatement"),Object.assign((function(e){if(Tr.env.BABEL_TYPES_8_BREAKING&&!e.handler&&!e.finalizer)throw new TypeError("TryStatement expects either a handler or finalizer, or both")}),{oneOfNodeTypes:["BlockStatement"]}))},handler:{optional:!0,validate:Ha("CatchClause")},finalizer:{optional:!0,validate:Ha("BlockStatement")}}}),tn("UnaryExpression",{builder:["operator","argument","prefix"],fields:{prefix:{default:!0},argument:{validate:Ha("Expression")},operator:{validate:Va.apply(void 0,b(wa))}},visitor:["argument"],aliases:["UnaryLike","Expression"]}),tn("UpdateExpression",{builder:["operator","argument","prefix"],fields:{prefix:{default:!1},argument:{validate:Tr.env.BABEL_TYPES_8_BREAKING?Ha("Identifier","MemberExpression"):Ha("Expression")},operator:{validate:Va.apply(void 0,b(fa))}},visitor:["argument"],aliases:["Expression"]}),tn("VariableDeclaration",{builder:["kind","declarations"],visitor:["declarations"],aliases:["Statement","Declaration"],fields:{declare:{validate:za("boolean"),optional:!0},kind:{validate:Va("var","let","const","using","await using")},declarations:{validate:Ja(za("array"),Ga(Ha("VariableDeclarator")))}},validate:function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING&&Dr("ForXStatement",e,{left:r})&&1!==r.declarations.length)throw new TypeError("Exactly one VariableDeclarator is required in the VariableDeclaration of a "+e.type)}}),tn("VariableDeclarator",{visitor:["id","init"],fields:{id:{validate:function(){if(!Tr.env.BABEL_TYPES_8_BREAKING)return Ha("LVal");var e=Ha("Identifier","ArrayPattern","ObjectPattern"),t=Ha("Identifier");return function(r,a,n){(r.init?e:t)(r,a,n)}}()},definite:{optional:!0,validate:za("boolean")},init:{optional:!0,validate:Ha("Expression")}}}),tn("WhileStatement",{visitor:["test","body"],aliases:["Statement","BlockParent","Loop","While","Scopable"],fields:{test:{validate:Ha("Expression")},body:{validate:Ha("Statement")}}}),tn("WithStatement",{visitor:["object","body"],aliases:["Statement"],fields:{object:{validate:Ha("Expression")},body:{validate:Ha("Statement")}}}),tn("AssignmentPattern",{visitor:["left","right","decorators"],builder:["left","right"],aliases:["Pattern","PatternLike","LVal"],fields:Object.assign({},un(),{left:{validate:Ha("Identifier","ObjectPattern","ArrayPattern","MemberExpression","TSAsExpression","TSSatisfiesExpression","TSTypeAssertion","TSNonNullExpression")},right:{validate:Ha("Expression")},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0}})}),tn("ArrayPattern",{visitor:["elements","typeAnnotation"],builder:["elements"],aliases:["Pattern","PatternLike","LVal"],fields:Object.assign({},un(),{elements:{validate:Ja(za("array"),Ga(Ka("null","PatternLike","LVal")))}})}),tn("ArrowFunctionExpression",{builder:["params","body","async"],visitor:["params","body","returnType","typeParameters"],aliases:["Scopable","Function","BlockParent","FunctionParent","Expression","Pureish"],fields:Object.assign({},rn(),an(),{expression:{validate:za("boolean")},body:{validate:Ha("BlockStatement","Expression")},predicate:{validate:Ha("DeclaredPredicate","InferredPredicate"),optional:!0}})}),tn("ClassBody",{visitor:["body"],fields:{body:{validate:Ja(za("array"),Ga(Ha("ClassMethod","ClassPrivateMethod","ClassProperty","ClassPrivateProperty","ClassAccessorProperty","TSDeclareMethod","TSIndexSignature","StaticBlock")))}}}),tn("ClassExpression",{builder:["id","superClass","body","decorators"],visitor:["id","body","superClass","mixins","typeParameters","superTypeParameters","implements","decorators"],aliases:["Scopable","Class","Expression"],fields:{id:{validate:Ha("Identifier"),optional:!0},typeParameters:{validate:Ha("TypeParameterDeclaration","TSTypeParameterDeclaration","Noop"),optional:!0},body:{validate:Ha("ClassBody")},superClass:{optional:!0,validate:Ha("Expression")},superTypeParameters:{validate:Ha("TypeParameterInstantiation","TSTypeParameterInstantiation"),optional:!0},implements:{validate:Ja(za("array"),Ga(Ha("TSExpressionWithTypeArguments","ClassImplements"))),optional:!0},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0},mixins:{validate:Ha("InterfaceExtends"),optional:!0}}}),tn("ClassDeclaration",{inherits:"ClassExpression",aliases:["Scopable","Class","Statement","Declaration"],fields:{id:{validate:Ha("Identifier"),optional:!0},typeParameters:{validate:Ha("TypeParameterDeclaration","TSTypeParameterDeclaration","Noop"),optional:!0},body:{validate:Ha("ClassBody")},superClass:{optional:!0,validate:Ha("Expression")},superTypeParameters:{validate:Ha("TypeParameterInstantiation","TSTypeParameterInstantiation"),optional:!0},implements:{validate:Ja(za("array"),Ga(Ha("TSExpressionWithTypeArguments","ClassImplements"))),optional:!0},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0},mixins:{validate:Ha("InterfaceExtends"),optional:!0},declare:{validate:za("boolean"),optional:!0},abstract:{validate:za("boolean"),optional:!0}},validate:function(){var e=Ha("Identifier");return function(t,r,a){Tr.env.BABEL_TYPES_8_BREAKING&&(Dr("ExportDefaultDeclaration",t)||e(a,"id",a.id))}}()}),tn("ExportAllDeclaration",{builder:["source"],visitor:["source","attributes","assertions"],aliases:["Statement","Declaration","ImportOrExportDeclaration","ExportDeclaration"],fields:{source:{validate:Ha("StringLiteral")},exportKind:Fa(Va("type","value")),attributes:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},assertions:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))}}}),tn("ExportDefaultDeclaration",{visitor:["declaration"],aliases:["Statement","Declaration","ImportOrExportDeclaration","ExportDeclaration"],fields:{declaration:{validate:Ha("TSDeclareFunction","FunctionDeclaration","ClassDeclaration","Expression")},exportKind:Fa(Va("value"))}}),tn("ExportNamedDeclaration",{builder:["declaration","specifiers","source"],visitor:["declaration","specifiers","source","attributes","assertions"],aliases:["Statement","Declaration","ImportOrExportDeclaration","ExportDeclaration"],fields:{declaration:{optional:!0,validate:Ja(Ha("Declaration"),Object.assign((function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING&&r&&e.specifiers.length)throw new TypeError("Only declaration or specifiers is allowed on ExportNamedDeclaration")}),{oneOfNodeTypes:["Declaration"]}),(function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING&&r&&e.source)throw new TypeError("Cannot export a declaration from a source")}))},attributes:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},assertions:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},specifiers:{default:[],validate:Ja(za("array"),Ga((cn=Ha("ExportSpecifier","ExportDefaultSpecifier","ExportNamespaceSpecifier"),ln=Ha("ExportSpecifier"),Tr.env.BABEL_TYPES_8_BREAKING?function(e,t,r){(e.source?cn:ln)(e,t,r)}:cn)))},source:{validate:Ha("StringLiteral"),optional:!0},exportKind:Fa(Va("type","value"))}}),tn("ExportSpecifier",{visitor:["local","exported"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")},exported:{validate:Ha("Identifier","StringLiteral")},exportKind:{validate:Va("type","value"),optional:!0}}}),tn("ForOfStatement",{visitor:["left","right","body"],builder:["left","right","body","await"],aliases:["Scopable","Statement","For","BlockParent","Loop","ForXStatement"],fields:{left:{validate:function(){if(!Tr.env.BABEL_TYPES_8_BREAKING)return Ha("VariableDeclaration","LVal");var e=Ha("VariableDeclaration"),t=Ha("Identifier","MemberExpression","ArrayPattern","ObjectPattern","TSAsExpression","TSSatisfiesExpression","TSTypeAssertion","TSNonNullExpression");return function(r,a,n){Dr("VariableDeclaration",n)?e(r,a,n):t(r,a,n)}}()},right:{validate:Ha("Expression")},body:{validate:Ha("Statement")},await:{default:!1}}}),tn("ImportDeclaration",{builder:["specifiers","source"],visitor:["specifiers","source","attributes","assertions"],aliases:["Statement","Declaration","ImportOrExportDeclaration"],fields:{attributes:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},assertions:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},module:{optional:!0,validate:za("boolean")},phase:{default:null,validate:Va("source","defer")},specifiers:{validate:Ja(za("array"),Ga(Ha("ImportSpecifier","ImportDefaultSpecifier","ImportNamespaceSpecifier")))},source:{validate:Ha("StringLiteral")},importKind:{validate:Va("type","typeof","value"),optional:!0}}}),tn("ImportDefaultSpecifier",{visitor:["local"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")}}}),tn("ImportNamespaceSpecifier",{visitor:["local"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")}}}),tn("ImportSpecifier",{visitor:["local","imported"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")},imported:{validate:Ha("Identifier","StringLiteral")},importKind:{validate:Va("type","typeof","value"),optional:!0}}}),tn("ImportExpression",{visitor:["source","options"],aliases:["Expression"],fields:{phase:{default:null,validate:Va("source","defer")},source:{validate:Ha("Expression")},options:{validate:Ha("Expression"),optional:!0}}}),tn("MetaProperty",{visitor:["meta","property"],aliases:["Expression"],fields:{meta:{validate:Ja(Ha("Identifier"),Object.assign((function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING){var a;switch(r.name){case"function":a="sent";break;case"new":a="target";break;case"import":a="meta"}if(!Dr("Identifier",e.property,{name:a}))throw new TypeError("Unrecognised MetaProperty")}}),{oneOfNodeTypes:["Identifier"]}))},property:{validate:Ha("Identifier")}}});var pn=function(){return{abstract:{validate:za("boolean"),optional:!0},accessibility:{validate:Va("public","private","protected"),optional:!0},static:{default:!1},override:{default:!1},computed:{default:!1},optional:{validate:za("boolean"),optional:!0},key:{validate:Ja(function(){var e=Ha("Identifier","StringLiteral","NumericLiteral","BigIntLiteral"),t=Ha("Expression");return function(r,a,n){(r.computed?t:e)(r,a,n)}}(),Ha("Identifier","StringLiteral","NumericLiteral","BigIntLiteral","Expression"))}}},fn=function(){return Object.assign({},rn(),pn(),{params:{validate:Ja(za("array"),Ga(Ha("Identifier","Pattern","RestElement","TSParameterProperty")))},kind:{validate:Va("get","set","method","constructor"),default:"method"},access:{validate:Ja(za("string"),Va("public","private","protected")),optional:!0},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0}})};tn("ClassMethod",{aliases:["Function","Scopable","BlockParent","FunctionParent","Method"],builder:["kind","key","params","body","computed","static","generator","async"],visitor:["key","params","body","decorators","returnType","typeParameters"],fields:Object.assign({},fn(),an(),{body:{validate:Ha("BlockStatement")}})}),tn("ObjectPattern",{visitor:["properties","typeAnnotation","decorators"],builder:["properties"],aliases:["Pattern","PatternLike","LVal"],fields:Object.assign({},un(),{properties:{validate:Ja(za("array"),Ga(Ha("RestElement","ObjectProperty")))}})}),tn("SpreadElement",{visitor:["argument"],aliases:["UnaryLike"],deprecatedAlias:"SpreadProperty",fields:{argument:{validate:Ha("Expression")}}}),tn("Super",{aliases:["Expression"]}),tn("TaggedTemplateExpression",{visitor:["tag","quasi","typeParameters"],builder:["tag","quasi"],aliases:["Expression"],fields:{tag:{validate:Ha("Expression")},quasi:{validate:Ha("TemplateLiteral")},typeParameters:{validate:Ha("TypeParameterInstantiation","TSTypeParameterInstantiation"),optional:!0}}}),tn("TemplateElement",{builder:["value","tail"],fields:{value:{validate:Ja(function(e){function t(t,r,a){for(var n=[],s=0,i=Object.keys(e);s=0)){if(Fe(o))return[o];if(zt(o))a.set(o.type,o);else if($e(o))n.has(o.types)||(t.push.apply(t,b(o.types)),n.add(o.types));else if(Ge(o)){var d=Nc(o.id);if(r.has(d)){var c,l=r.get(d);if(l.typeParameters){if(o.typeParameters)(c=l.typeParameters.params).push.apply(c,b(o.typeParameters.params)),l.typeParameters.params=Bc(l.typeParameters.params)}else l=o.typeParameters}else r.set(d,o)}else s.push(o)}}for(var u,p=j(a);!(u=p()).done;){var f=v(u.value,2)[1];s.push(f)}for(var g,y=j(r);!(g=y()).done;){var m=v(g.value,2)[1];s.push(m)}return s}function Mc(e){var t=Bc(e);return 1===t.length?t[0]:Ro(t)}function Lc(e){return F(e)?e.name:e.right.name+"."+Lc(e.left)}function Fc(e){for(var t=Array.from(e),r=new Map,a=new Map,n=new Set,s=[],i=0;i=0)){if(mt(o))return[o];if(Yt(o))a.set(o.type,o);else if(bt(o))n.has(o.types)||(t.push.apply(t,b(o.types)),n.add(o.types));else if(ht(o)&&o.typeParameters){var d=Lc(o.typeName);if(r.has(d)){var c,l=r.get(d);if(l.typeParameters){if(o.typeParameters)(c=l.typeParameters.params).push.apply(c,b(o.typeParameters.params)),l.typeParameters.params=Fc(l.typeParameters.params)}else l=o.typeParameters}else r.set(d,o)}else s.push(o)}}for(var u,p=j(a);!(u=p()).done;){var f=v(u.value,2)[1];s.push(f)}for(var g,y=j(r);!(g=y()).done;){var m=v(g.value,2)[1];s.push(m)}return s}function Uc(e){var t=e.map((function(e){return Pt(e)?e.typeAnnotation:e})),r=Fc(t);return 1===r.length?r[0]:Kd(r)}function qc(){return Ds("void",fs(0),!0)}var Wc=Function.call.bind(Object.prototype.hasOwnProperty);function Gc(e,t,r,a){return e&&"string"==typeof e.type?Kc(e,t,r,a):e}function Vc(e,t,r,a){return Array.isArray(e)?e.map((function(e){return Gc(e,t,r,a)})):Gc(e,t,r,a)}function Hc(e,t,r){return void 0===t&&(t=!0),void 0===r&&(r=!1),Kc(e,t,r,new Map)}function Kc(e,t,r,a){if(void 0===t&&(t=!0),void 0===r&&(r=!1),!e)return e;var n=e.type,s={type:e.type};if(F(e))s.name=e.name,Wc(e,"optional")&&"boolean"==typeof e.optional&&(s.optional=e.optional),Wc(e,"typeAnnotation")&&(s.typeAnnotation=t?Vc(e.typeAnnotation,!0,r,a):e.typeAnnotation);else{if(!Wc(_a,n))throw new Error('Unknown node type: "'+n+'"');for(var i=0,o=Object.keys(_a[n]);i=Number.MAX_SAFE_INTEGER?cu.uid=0:cu.uid++};var uu=Function.call.bind(Object.prototype.toString);function pu(e){if(void 0===e)return cs("undefined");if(!0===e||!1===e)return ys(e);if(null===e)return{type:"NullLiteral"};if("string"==typeof e)return ps(e);if("number"==typeof e){var t;if(Number.isFinite(e))t=fs(Math.abs(e));else t=Vn("/",Number.isNaN(e)?fs(0):fs(1),fs(0));return(e<0||Object.is(e,-0))&&(t=Ds("-",t)),t}if(function(e){return"[object RegExp]"===uu(e)}(e))return ms(e.source,e.toString().match(/\/([a-z]+|)$/)[1]);if(Array.isArray(e))return Wn(e.map(pu));if(function(e){if("object"!=typeof e||null===e||"[object Object]"!==Object.prototype.toString.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||null===Object.getPrototypeOf(t)}(e)){for(var r=[],a=0,n=Object.keys(e);a=0)return!0}else if(s===e)return!0}return!1}function bu(e){return se(e)&&("var"!==e.kind||e[Ta])}function Ru(e){return M(e)||pe(e)||bu(e)}function xu(e,t,r){switch(t.type){case"MemberExpression":case"OptionalMemberExpression":return t.property===e?!!t.computed:t.object===e;case"JSXMemberExpression":return t.object===e;case"VariableDeclarator":return t.init===e;case"ArrowFunctionExpression":return t.body===e;case"PrivateName":case"LabeledStatement":case"CatchClause":case"RestElement":case"BreakStatement":case"ContinueStatement":case"FunctionDeclaration":case"FunctionExpression":case"ExportNamespaceSpecifier":case"ExportDefaultSpecifier":case"ImportDefaultSpecifier":case"ImportNamespaceSpecifier":case"ImportSpecifier":case"ImportAttribute":case"JSXAttribute":case"ObjectPattern":case"ArrayPattern":case"MetaProperty":return!1;case"ClassMethod":case"ClassPrivateMethod":case"ObjectMethod":return t.key===e&&!!t.computed;case"ObjectProperty":return t.key===e?!!t.computed:!r||"ObjectPattern"!==r.type;case"ClassProperty":case"ClassAccessorProperty":case"TSPropertySignature":return t.key!==e||!!t.computed;case"ClassPrivateProperty":case"ObjectTypeProperty":return t.key!==e;case"ClassDeclaration":case"ClassExpression":return t.superClass===e;case"AssignmentExpression":case"AssignmentPattern":return t.right===e;case"ExportSpecifier":return(null==r||!r.source)&&t.local===e;case"TSEnumMember":return t.id!==e}return!0}function ju(e,t){return(!C(e)||!Ot(t)&&!I(t))&&(!(!Ut(e)||!Ot(t)&&!I(t))||Ct(e))}gu.keys={DeclareClass:["id"],DeclareFunction:["id"],DeclareModule:["id"],DeclareVariable:["id"],DeclareInterface:["id"],DeclareTypeAlias:["id"],DeclareOpaqueType:["id"],InterfaceDeclaration:["id"],TypeAlias:["id"],OpaqueType:["id"],CatchClause:["param"],LabeledStatement:["label"],UnaryExpression:["argument"],AssignmentExpression:["left"],ImportSpecifier:["local"],ImportNamespaceSpecifier:["local"],ImportDefaultSpecifier:["local"],ImportDeclaration:["specifiers"],ExportSpecifier:["exported"],ExportNamespaceSpecifier:["exported"],ExportDefaultSpecifier:["exported"],FunctionDeclaration:["id","params"],FunctionExpression:["id","params"],ArrowFunctionExpression:["params"],ObjectMethod:["params"],ClassMethod:["params"],ClassPrivateMethod:["params"],ForInStatement:["left"],ForOfStatement:["left"],ClassDeclaration:["id"],ClassExpression:["id"],RestElement:["argument"],UpdateExpression:["argument"],ObjectProperty:["value"],AssignmentPattern:["left"],ArrayPattern:["elements"],ObjectPattern:["properties"],VariableDeclaration:["declarations"],VariableDeclarator:["id"]};var Eu=new Set(["abstract","boolean","byte","char","double","enum","final","float","goto","implements","int","interface","long","native","package","private","protected","public","short","static","synchronized","throws","transient","volatile"]);function wu(e){return ea(e)&&!Eu.has(e)}function Su(e){return se(e,{kind:"var"})&&!e[Ta]}var Tu={isReactComponent:Zt,isCompatTag:function(e){return!!e&&/^[a-z]/.test(e)},buildChildren:function(e){for(var t=[],r=0;r=1.5*r;return Math.round(e/r)+" "+a+(n?"s":"")}return Rp=function(o,d){d=d||{};var c=typeof o;if("string"===c&&o.length>0)return function(i){if((i=String(i)).length>100)return;var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(i);if(!o)return;var d=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return d*s;case"weeks":case"week":case"w":return d*n;case"days":case"day":case"d":return d*a;case"hours":case"hour":case"hrs":case"hr":case"h":return d*r;case"minutes":case"minute":case"mins":case"min":case"m":return d*t;case"seconds":case"second":case"secs":case"sec":case"s":return d*e;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return d;default:return}}(o);if("number"===c&&isFinite(o))return d.long?function(n){var s=Math.abs(n);if(s>=a)return i(n,s,a,"day");if(s>=r)return i(n,s,r,"hour");if(s>=t)return i(n,s,t,"minute");if(s>=e)return i(n,s,e,"second");return n+" ms"}(o):function(n){var s=Math.abs(n);if(s>=a)return Math.round(n/a)+"d";if(s>=r)return Math.round(n/r)+"h";if(s>=t)return Math.round(n/t)+"m";if(s>=e)return Math.round(n/e)+"s";return n+"ms"}(o);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(o))},Rp}var Sp=function(e){function t(e){var a,n,s,i=null;function o(){for(var e=arguments.length,r=new Array(e),n=0;n=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(r=!1,function(){r||(r=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||function(){},e.exports=Sp(t),e.exports.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}}(Ep,Ep.exports);var Tp=Ep.exports,Pp=Hc,Ap=zs,kp=Xs,Cp=cs,_p=Ns,Ip=Bs;function Dp(e){if(!e.isExportDeclaration()||e.isExportAllDeclaration())throw new Error("Only default and named export declarations can be split.");if(e.isExportDefaultDeclaration()){var t=e.get("declaration"),r=t.isFunctionDeclaration()||t.isClassDeclaration(),a=t.isFunctionExpression()||t.isClassExpression(),n=t.isScope()?t.scope.parent:t.scope,s=t.node.id,i=!1;s?a&&n.hasBinding(s.name)&&(i=!0,s=n.generateUidIdentifier(s.name)):(i=!0,s=n.generateUidIdentifier("default"),(r||a)&&(t.node.id=Pp(s)));var o=r?t.node:_p("var",[Ip(Pp(s),t.node)]),d=Ap(null,[kp(Pp(s),Cp("default"))]);return e.insertAfter(d),e.replaceWith(o),i&&n.registerDeclaration(e),e}if(e.get("specifiers").length>0)throw new Error("It doesn't make sense to split exported specifiers.");var c=e.get("declaration"),l=c.getOuterBindingIdentifiers(),u=Object.keys(l).map((function(e){return kp(Cp(e),Cp(e))})),p=Ap(null,u);return e.insertAfter(p),e.replaceWith(c.node),e}function Op(e){var t=e.context,r=e.node;if(r.computed&&t.maybeQueue(e.get("key")),r.decorators)for(var a,n=j(e.get("decorators"));!(a=n()).done;){var s=a.value;t.maybeQueue(s)}}var Np={FunctionParent:function(e){e.isArrowFunctionExpression()||(e.skip(),e.isMethod()&&Op(e))},Property:function(e){e.isObjectProperty()||(e.skip(),Op(e))}},Bp={ReferencedIdentifier:function(e,t){var r=e.node;r.name===t.oldName&&(r.name=t.newName)},Scope:function(e,t){e.scope.bindingIdentifierEquals(t.oldName,t.binding.identifier)||(e.skip(),e.isMethod()&&Op(e))},ObjectProperty:function(e,t){var r,a=e.node,n=e.scope,s=a.key.name;!a.shorthand||s!==t.oldName&&s!==t.newName||n.getBindingIdentifier(s)!==t.binding.identifier||(a.shorthand=!1,null!=(r=a.extra)&&r.shorthand&&(a.extra.shorthand=!1))},"AssignmentExpression|Declaration|VariableDeclarator":function(e,t){if(!e.isVariableDeclaration()){var r=e.getOuterBindingIdentifiers();for(var a in r)a===t.oldName&&(r[a].name=t.newName)}}},Mp=function(){function e(e,t,r){this.newName=r,this.oldName=t,this.binding=e}var t=e.prototype;return t.maybeConvertFromExportDeclaration=function(e){var t=e.parentPath;if(t.isExportDeclaration()){if(t.isExportDefaultDeclaration()){var r=t.node.declaration;if(Bt(r)&&!r.id)return}t.isExportAllDeclaration()||Dp(t)}},t.maybeConvertFromClassFunctionDeclaration=function(e){return e},t.maybeConvertFromClassFunctionExpression=function(e){return e},t.rename=function(){var e=this.binding,t=this.oldName,r=this.newName,a=e.scope,n=e.path,s=n.find((function(e){return e.isDeclaration()||e.isFunctionExpression()||e.isClassExpression()}));s&&(s.getOuterBindingIdentifiers()[t]===e.identifier&&this.maybeConvertFromExportDeclaration(s));AP(arguments[0]||a.block,ap(Bp),a,this,a.path,{discriminant:!0}),arguments[0]||(a.removeOwnBinding(t),a.bindings[r]=e,this.binding.identifier.name=r),s&&(this.maybeConvertFromClassFunctionDeclaration(n),this.maybeConvertFromClassFunctionExpression(n))},d(e)}(),Lp=function(){function e(e){var t=e.identifier,r=e.scope,a=e.path,n=e.kind;this.identifier=void 0,this.scope=void 0,this.path=void 0,this.kind=void 0,this.constantViolations=[],this.constant=!0,this.referencePaths=[],this.referenced=!1,this.references=0,this.identifier=t,this.scope=r,this.path=a,this.kind=n,"var"!==n&&"hoisted"!==n||!function(e){for(var t=e.parentPath,r=e.key;t;t=(a=t).parentPath,r=a.key,a){var a;if(t.isFunctionParent())return!1;if(t.isWhile()||t.isForXStatement()||t.isForStatement()&&"body"===r)return!0}return!1}(a)||this.reassign(a),this.clearValue()}var t=e.prototype;return t.deoptValue=function(){this.clearValue(),this.hasDeoptedValue=!0},t.setValue=function(e){this.hasDeoptedValue||(this.hasValue=!0,this.value=e)},t.clearValue=function(){this.hasDeoptedValue=!1,this.hasValue=!1,this.value=null},t.reassign=function(e){this.constant=!1,-1===this.constantViolations.indexOf(e)&&this.constantViolations.push(e)},t.reference=function(e){-1===this.referencePaths.indexOf(e)&&(this.referenced=!0,this.references++,this.referencePaths.push(e))},t.dereference=function(){this.references--,this.referenced=!!this.references},d(e)}();var Fp,Up,qp={builtin:{Array:!1,ArrayBuffer:!1,Atomics:!1,BigInt:!1,BigInt64Array:!1,BigUint64Array:!1,Boolean:!1,constructor:!1,DataView:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Float32Array:!1,Float64Array:!1,Function:!1,globalThis:!1,hasOwnProperty:!1,Infinity:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Map:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,Promise:!1,propertyIsEnumerable:!1,Proxy:!1,RangeError:!1,ReferenceError:!1,Reflect:!1,RegExp:!1,Set:!1,SharedArrayBuffer:!1,String:!1,Symbol:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1,WeakMap:!1,WeakSet:!1},es5:{Array:!1,Boolean:!1,constructor:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Function:!1,hasOwnProperty:!1,Infinity:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,propertyIsEnumerable:!1,RangeError:!1,ReferenceError:!1,RegExp:!1,String:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1},es2015:{Array:!1,ArrayBuffer:!1,Boolean:!1,constructor:!1,DataView:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Float32Array:!1,Float64Array:!1,Function:!1,hasOwnProperty:!1,Infinity:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Map:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,Promise:!1,propertyIsEnumerable:!1,Proxy:!1,RangeError:!1,ReferenceError:!1,Reflect:!1,RegExp:!1,Set:!1,String:!1,Symbol:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1,WeakMap:!1,WeakSet:!1},es2017:{Array:!1,ArrayBuffer:!1,Atomics:!1,Boolean:!1,constructor:!1,DataView:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Float32Array:!1,Float64Array:!1,Function:!1,hasOwnProperty:!1,Infinity:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Map:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,Promise:!1,propertyIsEnumerable:!1,Proxy:!1,RangeError:!1,ReferenceError:!1,Reflect:!1,RegExp:!1,Set:!1,SharedArrayBuffer:!1,String:!1,Symbol:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1,WeakMap:!1,WeakSet:!1},browser:{AbortController:!1,AbortSignal:!1,addEventListener:!1,alert:!1,AnalyserNode:!1,Animation:!1,AnimationEffectReadOnly:!1,AnimationEffectTiming:!1,AnimationEffectTimingReadOnly:!1,AnimationEvent:!1,AnimationPlaybackEvent:!1,AnimationTimeline:!1,applicationCache:!1,ApplicationCache:!1,ApplicationCacheErrorEvent:!1,atob:!1,Attr:!1,Audio:!1,AudioBuffer:!1,AudioBufferSourceNode:!1,AudioContext:!1,AudioDestinationNode:!1,AudioListener:!1,AudioNode:!1,AudioParam:!1,AudioProcessingEvent:!1,AudioScheduledSourceNode:!1,"AudioWorkletGlobalScope ":!1,AudioWorkletNode:!1,AudioWorkletProcessor:!1,BarProp:!1,BaseAudioContext:!1,BatteryManager:!1,BeforeUnloadEvent:!1,BiquadFilterNode:!1,Blob:!1,BlobEvent:!1,blur:!1,BroadcastChannel:!1,btoa:!1,BudgetService:!1,ByteLengthQueuingStrategy:!1,Cache:!1,caches:!1,CacheStorage:!1,cancelAnimationFrame:!1,cancelIdleCallback:!1,CanvasCaptureMediaStreamTrack:!1,CanvasGradient:!1,CanvasPattern:!1,CanvasRenderingContext2D:!1,ChannelMergerNode:!1,ChannelSplitterNode:!1,CharacterData:!1,clearInterval:!1,clearTimeout:!1,clientInformation:!1,ClipboardEvent:!1,close:!1,closed:!1,CloseEvent:!1,Comment:!1,CompositionEvent:!1,confirm:!1,console:!1,ConstantSourceNode:!1,ConvolverNode:!1,CountQueuingStrategy:!1,createImageBitmap:!1,Credential:!1,CredentialsContainer:!1,crypto:!1,Crypto:!1,CryptoKey:!1,CSS:!1,CSSConditionRule:!1,CSSFontFaceRule:!1,CSSGroupingRule:!1,CSSImportRule:!1,CSSKeyframeRule:!1,CSSKeyframesRule:!1,CSSMediaRule:!1,CSSNamespaceRule:!1,CSSPageRule:!1,CSSRule:!1,CSSRuleList:!1,CSSStyleDeclaration:!1,CSSStyleRule:!1,CSSStyleSheet:!1,CSSSupportsRule:!1,CustomElementRegistry:!1,customElements:!1,CustomEvent:!1,DataTransfer:!1,DataTransferItem:!1,DataTransferItemList:!1,defaultstatus:!1,defaultStatus:!1,DelayNode:!1,DeviceMotionEvent:!1,DeviceOrientationEvent:!1,devicePixelRatio:!1,dispatchEvent:!1,document:!1,Document:!1,DocumentFragment:!1,DocumentType:!1,DOMError:!1,DOMException:!1,DOMImplementation:!1,DOMMatrix:!1,DOMMatrixReadOnly:!1,DOMParser:!1,DOMPoint:!1,DOMPointReadOnly:!1,DOMQuad:!1,DOMRect:!1,DOMRectReadOnly:!1,DOMStringList:!1,DOMStringMap:!1,DOMTokenList:!1,DragEvent:!1,DynamicsCompressorNode:!1,Element:!1,ErrorEvent:!1,event:!1,Event:!1,EventSource:!1,EventTarget:!1,external:!1,fetch:!1,File:!1,FileList:!1,FileReader:!1,find:!1,focus:!1,FocusEvent:!1,FontFace:!1,FontFaceSetLoadEvent:!1,FormData:!1,frameElement:!1,frames:!1,GainNode:!1,Gamepad:!1,GamepadButton:!1,GamepadEvent:!1,getComputedStyle:!1,getSelection:!1,HashChangeEvent:!1,Headers:!1,history:!1,History:!1,HTMLAllCollection:!1,HTMLAnchorElement:!1,HTMLAreaElement:!1,HTMLAudioElement:!1,HTMLBaseElement:!1,HTMLBodyElement:!1,HTMLBRElement:!1,HTMLButtonElement:!1,HTMLCanvasElement:!1,HTMLCollection:!1,HTMLContentElement:!1,HTMLDataElement:!1,HTMLDataListElement:!1,HTMLDetailsElement:!1,HTMLDialogElement:!1,HTMLDirectoryElement:!1,HTMLDivElement:!1,HTMLDListElement:!1,HTMLDocument:!1,HTMLElement:!1,HTMLEmbedElement:!1,HTMLFieldSetElement:!1,HTMLFontElement:!1,HTMLFormControlsCollection:!1,HTMLFormElement:!1,HTMLFrameElement:!1,HTMLFrameSetElement:!1,HTMLHeadElement:!1,HTMLHeadingElement:!1,HTMLHRElement:!1,HTMLHtmlElement:!1,HTMLIFrameElement:!1,HTMLImageElement:!1,HTMLInputElement:!1,HTMLLabelElement:!1,HTMLLegendElement:!1,HTMLLIElement:!1,HTMLLinkElement:!1,HTMLMapElement:!1,HTMLMarqueeElement:!1,HTMLMediaElement:!1,HTMLMenuElement:!1,HTMLMetaElement:!1,HTMLMeterElement:!1,HTMLModElement:!1,HTMLObjectElement:!1,HTMLOListElement:!1,HTMLOptGroupElement:!1,HTMLOptionElement:!1,HTMLOptionsCollection:!1,HTMLOutputElement:!1,HTMLParagraphElement:!1,HTMLParamElement:!1,HTMLPictureElement:!1,HTMLPreElement:!1,HTMLProgressElement:!1,HTMLQuoteElement:!1,HTMLScriptElement:!1,HTMLSelectElement:!1,HTMLShadowElement:!1,HTMLSlotElement:!1,HTMLSourceElement:!1,HTMLSpanElement:!1,HTMLStyleElement:!1,HTMLTableCaptionElement:!1,HTMLTableCellElement:!1,HTMLTableColElement:!1,HTMLTableElement:!1,HTMLTableRowElement:!1,HTMLTableSectionElement:!1,HTMLTemplateElement:!1,HTMLTextAreaElement:!1,HTMLTimeElement:!1,HTMLTitleElement:!1,HTMLTrackElement:!1,HTMLUListElement:!1,HTMLUnknownElement:!1,HTMLVideoElement:!1,IDBCursor:!1,IDBCursorWithValue:!1,IDBDatabase:!1,IDBFactory:!1,IDBIndex:!1,IDBKeyRange:!1,IDBObjectStore:!1,IDBOpenDBRequest:!1,IDBRequest:!1,IDBTransaction:!1,IDBVersionChangeEvent:!1,IdleDeadline:!1,IIRFilterNode:!1,Image:!1,ImageBitmap:!1,ImageBitmapRenderingContext:!1,ImageCapture:!1,ImageData:!1,indexedDB:!1,innerHeight:!1,innerWidth:!1,InputEvent:!1,IntersectionObserver:!1,IntersectionObserverEntry:!1,Intl:!1,isSecureContext:!1,KeyboardEvent:!1,KeyframeEffect:!1,KeyframeEffectReadOnly:!1,length:!1,localStorage:!1,location:!0,Location:!1,locationbar:!1,matchMedia:!1,MediaDeviceInfo:!1,MediaDevices:!1,MediaElementAudioSourceNode:!1,MediaEncryptedEvent:!1,MediaError:!1,MediaKeyMessageEvent:!1,MediaKeySession:!1,MediaKeyStatusMap:!1,MediaKeySystemAccess:!1,MediaList:!1,MediaQueryList:!1,MediaQueryListEvent:!1,MediaRecorder:!1,MediaSettingsRange:!1,MediaSource:!1,MediaStream:!1,MediaStreamAudioDestinationNode:!1,MediaStreamAudioSourceNode:!1,MediaStreamEvent:!1,MediaStreamTrack:!1,MediaStreamTrackEvent:!1,menubar:!1,MessageChannel:!1,MessageEvent:!1,MessagePort:!1,MIDIAccess:!1,MIDIConnectionEvent:!1,MIDIInput:!1,MIDIInputMap:!1,MIDIMessageEvent:!1,MIDIOutput:!1,MIDIOutputMap:!1,MIDIPort:!1,MimeType:!1,MimeTypeArray:!1,MouseEvent:!1,moveBy:!1,moveTo:!1,MutationEvent:!1,MutationObserver:!1,MutationRecord:!1,name:!1,NamedNodeMap:!1,NavigationPreloadManager:!1,navigator:!1,Navigator:!1,NetworkInformation:!1,Node:!1,NodeFilter:!1,NodeIterator:!1,NodeList:!1,Notification:!1,OfflineAudioCompletionEvent:!1,OfflineAudioContext:!1,offscreenBuffering:!1,OffscreenCanvas:!0,onabort:!0,onafterprint:!0,onanimationend:!0,onanimationiteration:!0,onanimationstart:!0,onappinstalled:!0,onauxclick:!0,onbeforeinstallprompt:!0,onbeforeprint:!0,onbeforeunload:!0,onblur:!0,oncancel:!0,oncanplay:!0,oncanplaythrough:!0,onchange:!0,onclick:!0,onclose:!0,oncontextmenu:!0,oncuechange:!0,ondblclick:!0,ondevicemotion:!0,ondeviceorientation:!0,ondeviceorientationabsolute:!0,ondrag:!0,ondragend:!0,ondragenter:!0,ondragleave:!0,ondragover:!0,ondragstart:!0,ondrop:!0,ondurationchange:!0,onemptied:!0,onended:!0,onerror:!0,onfocus:!0,ongotpointercapture:!0,onhashchange:!0,oninput:!0,oninvalid:!0,onkeydown:!0,onkeypress:!0,onkeyup:!0,onlanguagechange:!0,onload:!0,onloadeddata:!0,onloadedmetadata:!0,onloadstart:!0,onlostpointercapture:!0,onmessage:!0,onmessageerror:!0,onmousedown:!0,onmouseenter:!0,onmouseleave:!0,onmousemove:!0,onmouseout:!0,onmouseover:!0,onmouseup:!0,onmousewheel:!0,onoffline:!0,ononline:!0,onpagehide:!0,onpageshow:!0,onpause:!0,onplay:!0,onplaying:!0,onpointercancel:!0,onpointerdown:!0,onpointerenter:!0,onpointerleave:!0,onpointermove:!0,onpointerout:!0,onpointerover:!0,onpointerup:!0,onpopstate:!0,onprogress:!0,onratechange:!0,onrejectionhandled:!0,onreset:!0,onresize:!0,onscroll:!0,onsearch:!0,onseeked:!0,onseeking:!0,onselect:!0,onstalled:!0,onstorage:!0,onsubmit:!0,onsuspend:!0,ontimeupdate:!0,ontoggle:!0,ontransitionend:!0,onunhandledrejection:!0,onunload:!0,onvolumechange:!0,onwaiting:!0,onwheel:!0,open:!1,openDatabase:!1,opener:!1,Option:!1,origin:!1,OscillatorNode:!1,outerHeight:!1,outerWidth:!1,PageTransitionEvent:!1,pageXOffset:!1,pageYOffset:!1,PannerNode:!1,parent:!1,Path2D:!1,PaymentAddress:!1,PaymentRequest:!1,PaymentRequestUpdateEvent:!1,PaymentResponse:!1,performance:!1,Performance:!1,PerformanceEntry:!1,PerformanceLongTaskTiming:!1,PerformanceMark:!1,PerformanceMeasure:!1,PerformanceNavigation:!1,PerformanceNavigationTiming:!1,PerformanceObserver:!1,PerformanceObserverEntryList:!1,PerformancePaintTiming:!1,PerformanceResourceTiming:!1,PerformanceTiming:!1,PeriodicWave:!1,Permissions:!1,PermissionStatus:!1,personalbar:!1,PhotoCapabilities:!1,Plugin:!1,PluginArray:!1,PointerEvent:!1,PopStateEvent:!1,postMessage:!1,Presentation:!1,PresentationAvailability:!1,PresentationConnection:!1,PresentationConnectionAvailableEvent:!1,PresentationConnectionCloseEvent:!1,PresentationConnectionList:!1,PresentationReceiver:!1,PresentationRequest:!1,print:!1,ProcessingInstruction:!1,ProgressEvent:!1,PromiseRejectionEvent:!1,prompt:!1,PushManager:!1,PushSubscription:!1,PushSubscriptionOptions:!1,queueMicrotask:!1,RadioNodeList:!1,Range:!1,ReadableStream:!1,registerProcessor:!1,RemotePlayback:!1,removeEventListener:!1,Request:!1,requestAnimationFrame:!1,requestIdleCallback:!1,resizeBy:!1,ResizeObserver:!1,ResizeObserverEntry:!1,resizeTo:!1,Response:!1,RTCCertificate:!1,RTCDataChannel:!1,RTCDataChannelEvent:!1,RTCDtlsTransport:!1,RTCIceCandidate:!1,RTCIceGatherer:!1,RTCIceTransport:!1,RTCPeerConnection:!1,RTCPeerConnectionIceEvent:!1,RTCRtpContributingSource:!1,RTCRtpReceiver:!1,RTCRtpSender:!1,RTCSctpTransport:!1,RTCSessionDescription:!1,RTCStatsReport:!1,RTCTrackEvent:!1,screen:!1,Screen:!1,screenLeft:!1,ScreenOrientation:!1,screenTop:!1,screenX:!1,screenY:!1,ScriptProcessorNode:!1,scroll:!1,scrollbars:!1,scrollBy:!1,scrollTo:!1,scrollX:!1,scrollY:!1,SecurityPolicyViolationEvent:!1,Selection:!1,self:!1,ServiceWorker:!1,ServiceWorkerContainer:!1,ServiceWorkerRegistration:!1,sessionStorage:!1,setInterval:!1,setTimeout:!1,ShadowRoot:!1,SharedWorker:!1,SourceBuffer:!1,SourceBufferList:!1,speechSynthesis:!1,SpeechSynthesisEvent:!1,SpeechSynthesisUtterance:!1,StaticRange:!1,status:!1,statusbar:!1,StereoPannerNode:!1,stop:!1,Storage:!1,StorageEvent:!1,StorageManager:!1,styleMedia:!1,StyleSheet:!1,StyleSheetList:!1,SubtleCrypto:!1,SVGAElement:!1,SVGAngle:!1,SVGAnimatedAngle:!1,SVGAnimatedBoolean:!1,SVGAnimatedEnumeration:!1,SVGAnimatedInteger:!1,SVGAnimatedLength:!1,SVGAnimatedLengthList:!1,SVGAnimatedNumber:!1,SVGAnimatedNumberList:!1,SVGAnimatedPreserveAspectRatio:!1,SVGAnimatedRect:!1,SVGAnimatedString:!1,SVGAnimatedTransformList:!1,SVGAnimateElement:!1,SVGAnimateMotionElement:!1,SVGAnimateTransformElement:!1,SVGAnimationElement:!1,SVGCircleElement:!1,SVGClipPathElement:!1,SVGComponentTransferFunctionElement:!1,SVGDefsElement:!1,SVGDescElement:!1,SVGDiscardElement:!1,SVGElement:!1,SVGEllipseElement:!1,SVGFEBlendElement:!1,SVGFEColorMatrixElement:!1,SVGFEComponentTransferElement:!1,SVGFECompositeElement:!1,SVGFEConvolveMatrixElement:!1,SVGFEDiffuseLightingElement:!1,SVGFEDisplacementMapElement:!1,SVGFEDistantLightElement:!1,SVGFEDropShadowElement:!1,SVGFEFloodElement:!1,SVGFEFuncAElement:!1,SVGFEFuncBElement:!1,SVGFEFuncGElement:!1,SVGFEFuncRElement:!1,SVGFEGaussianBlurElement:!1,SVGFEImageElement:!1,SVGFEMergeElement:!1,SVGFEMergeNodeElement:!1,SVGFEMorphologyElement:!1,SVGFEOffsetElement:!1,SVGFEPointLightElement:!1,SVGFESpecularLightingElement:!1,SVGFESpotLightElement:!1,SVGFETileElement:!1,SVGFETurbulenceElement:!1,SVGFilterElement:!1,SVGForeignObjectElement:!1,SVGGElement:!1,SVGGeometryElement:!1,SVGGradientElement:!1,SVGGraphicsElement:!1,SVGImageElement:!1,SVGLength:!1,SVGLengthList:!1,SVGLinearGradientElement:!1,SVGLineElement:!1,SVGMarkerElement:!1,SVGMaskElement:!1,SVGMatrix:!1,SVGMetadataElement:!1,SVGMPathElement:!1,SVGNumber:!1,SVGNumberList:!1,SVGPathElement:!1,SVGPatternElement:!1,SVGPoint:!1,SVGPointList:!1,SVGPolygonElement:!1,SVGPolylineElement:!1,SVGPreserveAspectRatio:!1,SVGRadialGradientElement:!1,SVGRect:!1,SVGRectElement:!1,SVGScriptElement:!1,SVGSetElement:!1,SVGStopElement:!1,SVGStringList:!1,SVGStyleElement:!1,SVGSVGElement:!1,SVGSwitchElement:!1,SVGSymbolElement:!1,SVGTextContentElement:!1,SVGTextElement:!1,SVGTextPathElement:!1,SVGTextPositioningElement:!1,SVGTitleElement:!1,SVGTransform:!1,SVGTransformList:!1,SVGTSpanElement:!1,SVGUnitTypes:!1,SVGUseElement:!1,SVGViewElement:!1,TaskAttributionTiming:!1,Text:!1,TextDecoder:!1,TextEncoder:!1,TextEvent:!1,TextMetrics:!1,TextTrack:!1,TextTrackCue:!1,TextTrackCueList:!1,TextTrackList:!1,TimeRanges:!1,toolbar:!1,top:!1,Touch:!1,TouchEvent:!1,TouchList:!1,TrackEvent:!1,TransitionEvent:!1,TreeWalker:!1,UIEvent:!1,URL:!1,URLSearchParams:!1,ValidityState:!1,visualViewport:!1,VisualViewport:!1,VTTCue:!1,WaveShaperNode:!1,WebAssembly:!1,WebGL2RenderingContext:!1,WebGLActiveInfo:!1,WebGLBuffer:!1,WebGLContextEvent:!1,WebGLFramebuffer:!1,WebGLProgram:!1,WebGLQuery:!1,WebGLRenderbuffer:!1,WebGLRenderingContext:!1,WebGLSampler:!1,WebGLShader:!1,WebGLShaderPrecisionFormat:!1,WebGLSync:!1,WebGLTexture:!1,WebGLTransformFeedback:!1,WebGLUniformLocation:!1,WebGLVertexArrayObject:!1,WebSocket:!1,WheelEvent:!1,window:!1,Window:!1,Worker:!1,WritableStream:!1,XMLDocument:!1,XMLHttpRequest:!1,XMLHttpRequestEventTarget:!1,XMLHttpRequestUpload:!1,XMLSerializer:!1,XPathEvaluator:!1,XPathExpression:!1,XPathResult:!1,XSLTProcessor:!1},worker:{addEventListener:!1,applicationCache:!1,atob:!1,Blob:!1,BroadcastChannel:!1,btoa:!1,Cache:!1,caches:!1,clearInterval:!1,clearTimeout:!1,close:!0,console:!1,fetch:!1,FileReaderSync:!1,FormData:!1,Headers:!1,IDBCursor:!1,IDBCursorWithValue:!1,IDBDatabase:!1,IDBFactory:!1,IDBIndex:!1,IDBKeyRange:!1,IDBObjectStore:!1,IDBOpenDBRequest:!1,IDBRequest:!1,IDBTransaction:!1,IDBVersionChangeEvent:!1,ImageData:!1,importScripts:!0,indexedDB:!1,location:!1,MessageChannel:!1,MessagePort:!1,name:!1,navigator:!1,Notification:!1,onclose:!0,onconnect:!0,onerror:!0,onlanguagechange:!0,onmessage:!0,onoffline:!0,ononline:!0,onrejectionhandled:!0,onunhandledrejection:!0,performance:!1,Performance:!1,PerformanceEntry:!1,PerformanceMark:!1,PerformanceMeasure:!1,PerformanceNavigation:!1,PerformanceResourceTiming:!1,PerformanceTiming:!1,postMessage:!0,Promise:!1,queueMicrotask:!1,removeEventListener:!1,Request:!1,Response:!1,self:!0,ServiceWorkerRegistration:!1,setInterval:!1,setTimeout:!1,TextDecoder:!1,TextEncoder:!1,URL:!1,URLSearchParams:!1,WebSocket:!1,Worker:!1,WorkerGlobalScope:!1,XMLHttpRequest:!1},node:{__dirname:!1,__filename:!1,Buffer:!1,clearImmediate:!1,clearInterval:!1,clearTimeout:!1,console:!1,exports:!0,global:!1,Intl:!1,module:!1,process:!1,queueMicrotask:!1,require:!1,setImmediate:!1,setInterval:!1,setTimeout:!1,TextDecoder:!1,TextEncoder:!1,URL:!1,URLSearchParams:!1},commonjs:{exports:!0,global:!1,module:!1,require:!1},amd:{define:!1,require:!1},mocha:{after:!1,afterEach:!1,before:!1,beforeEach:!1,context:!1,describe:!1,it:!1,mocha:!1,run:!1,setup:!1,specify:!1,suite:!1,suiteSetup:!1,suiteTeardown:!1,teardown:!1,test:!1,xcontext:!1,xdescribe:!1,xit:!1,xspecify:!1},jasmine:{afterAll:!1,afterEach:!1,beforeAll:!1,beforeEach:!1,describe:!1,expect:!1,fail:!1,fdescribe:!1,fit:!1,it:!1,jasmine:!1,pending:!1,runs:!1,spyOn:!1,spyOnProperty:!1,waits:!1,waitsFor:!1,xdescribe:!1,xit:!1},jest:{afterAll:!1,afterEach:!1,beforeAll:!1,beforeEach:!1,describe:!1,expect:!1,fdescribe:!1,fit:!1,it:!1,jest:!1,pit:!1,require:!1,test:!1,xdescribe:!1,xit:!1,xtest:!1},qunit:{asyncTest:!1,deepEqual:!1,equal:!1,expect:!1,module:!1,notDeepEqual:!1,notEqual:!1,notOk:!1,notPropEqual:!1,notStrictEqual:!1,ok:!1,propEqual:!1,QUnit:!1,raises:!1,start:!1,stop:!1,strictEqual:!1,test:!1,throws:!1},phantomjs:{console:!0,exports:!0,phantom:!0,require:!0,WebPage:!0},couch:{emit:!1,exports:!1,getRow:!1,log:!1,module:!1,provides:!1,require:!1,respond:!1,send:!1,start:!1,sum:!1},rhino:{defineClass:!1,deserialize:!1,gc:!1,help:!1,importClass:!1,importPackage:!1,java:!1,load:!1,loadClass:!1,Packages:!1,print:!1,quit:!1,readFile:!1,readUrl:!1,runCommand:!1,seal:!1,serialize:!1,spawn:!1,sync:!1,toint32:!1,version:!1},nashorn:{__DIR__:!1,__FILE__:!1,__LINE__:!1,com:!1,edu:!1,exit:!1,java:!1,Java:!1,javafx:!1,JavaImporter:!1,javax:!1,JSAdapter:!1,load:!1,loadWithNewGlobal:!1,org:!1,Packages:!1,print:!1,quit:!1},wsh:{ActiveXObject:!0,Enumerator:!0,GetObject:!0,ScriptEngine:!0,ScriptEngineBuildVersion:!0,ScriptEngineMajorVersion:!0,ScriptEngineMinorVersion:!0,VBArray:!0,WScript:!0,WSH:!0,XDomainRequest:!0},jquery:{$:!1,jQuery:!1},yui:{YAHOO:!1,YAHOO_config:!1,YUI:!1,YUI_config:!1},shelljs:{cat:!1,cd:!1,chmod:!1,config:!1,cp:!1,dirs:!1,echo:!1,env:!1,error:!1,exec:!1,exit:!1,find:!1,grep:!1,ln:!1,ls:!1,mkdir:!1,mv:!1,popd:!1,pushd:!1,pwd:!1,rm:!1,sed:!1,set:!1,target:!1,tempdir:!1,test:!1,touch:!1,which:!1},prototypejs:{$:!1,$$:!1,$A:!1,$break:!1,$continue:!1,$F:!1,$H:!1,$R:!1,$w:!1,Abstract:!1,Ajax:!1,Autocompleter:!1,Builder:!1,Class:!1,Control:!1,Draggable:!1,Draggables:!1,Droppables:!1,Effect:!1,Element:!1,Enumerable:!1,Event:!1,Field:!1,Form:!1,Hash:!1,Insertion:!1,ObjectRange:!1,PeriodicalExecuter:!1,Position:!1,Prototype:!1,Scriptaculous:!1,Selector:!1,Sortable:!1,SortableObserver:!1,Sound:!1,Template:!1,Toggle:!1,Try:!1},meteor:{_:!1,$:!1,Accounts:!1,AccountsClient:!1,AccountsCommon:!1,AccountsServer:!1,App:!1,Assets:!1,Blaze:!1,check:!1,Cordova:!1,DDP:!1,DDPRateLimiter:!1,DDPServer:!1,Deps:!1,EJSON:!1,Email:!1,HTTP:!1,Log:!1,Match:!1,Meteor:!1,Mongo:!1,MongoInternals:!1,Npm:!1,Package:!1,Plugin:!1,process:!1,Random:!1,ReactiveDict:!1,ReactiveVar:!1,Router:!1,ServiceConfiguration:!1,Session:!1,share:!1,Spacebars:!1,Template:!1,Tinytest:!1,Tracker:!1,UI:!1,Utils:!1,WebApp:!1,WebAppInternals:!1},mongo:{_isWindows:!1,_rand:!1,BulkWriteResult:!1,cat:!1,cd:!1,connect:!1,db:!1,getHostName:!1,getMemInfo:!1,hostname:!1,ISODate:!1,listFiles:!1,load:!1,ls:!1,md5sumFile:!1,mkdir:!1,Mongo:!1,NumberInt:!1,NumberLong:!1,ObjectId:!1,PlanCache:!1,print:!1,printjson:!1,pwd:!1,quit:!1,removeFile:!1,rs:!1,sh:!1,UUID:!1,version:!1,WriteResult:!1},applescript:{$:!1,Application:!1,Automation:!1,console:!1,delay:!1,Library:!1,ObjC:!1,ObjectSpecifier:!1,Path:!1,Progress:!1,Ref:!1},serviceworker:{addEventListener:!1,applicationCache:!1,atob:!1,Blob:!1,BroadcastChannel:!1,btoa:!1,Cache:!1,caches:!1,CacheStorage:!1,clearInterval:!1,clearTimeout:!1,Client:!1,clients:!1,Clients:!1,close:!0,console:!1,ExtendableEvent:!1,ExtendableMessageEvent:!1,fetch:!1,FetchEvent:!1,FileReaderSync:!1,FormData:!1,Headers:!1,IDBCursor:!1,IDBCursorWithValue:!1,IDBDatabase:!1,IDBFactory:!1,IDBIndex:!1,IDBKeyRange:!1,IDBObjectStore:!1,IDBOpenDBRequest:!1,IDBRequest:!1,IDBTransaction:!1,IDBVersionChangeEvent:!1,ImageData:!1,importScripts:!1,indexedDB:!1,location:!1,MessageChannel:!1,MessagePort:!1,name:!1,navigator:!1,Notification:!1,onclose:!0,onconnect:!0,onerror:!0,onfetch:!0,oninstall:!0,onlanguagechange:!0,onmessage:!0,onmessageerror:!0,onnotificationclick:!0,onnotificationclose:!0,onoffline:!0,ononline:!0,onpush:!0,onpushsubscriptionchange:!0,onrejectionhandled:!0,onsync:!0,onunhandledrejection:!0,performance:!1,Performance:!1,PerformanceEntry:!1,PerformanceMark:!1,PerformanceMeasure:!1,PerformanceNavigation:!1,PerformanceResourceTiming:!1,PerformanceTiming:!1,postMessage:!0,Promise:!1,queueMicrotask:!1,registration:!1,removeEventListener:!1,Request:!1,Response:!1,self:!1,ServiceWorker:!1,ServiceWorkerContainer:!1,ServiceWorkerGlobalScope:!1,ServiceWorkerMessageEvent:!1,ServiceWorkerRegistration:!1,setInterval:!1,setTimeout:!1,skipWaiting:!1,TextDecoder:!1,TextEncoder:!1,URL:!1,URLSearchParams:!1,WebSocket:!1,WindowClient:!1,Worker:!1,WorkerGlobalScope:!1,XMLHttpRequest:!1},atomtest:{advanceClock:!1,fakeClearInterval:!1,fakeClearTimeout:!1,fakeSetInterval:!1,fakeSetTimeout:!1,resetTimeouts:!1,waitsForPromise:!1},embertest:{andThen:!1,click:!1,currentPath:!1,currentRouteName:!1,currentURL:!1,fillIn:!1,find:!1,findAll:!1,findWithAssert:!1,keyEvent:!1,pauseTest:!1,resumeTest:!1,triggerEvent:!1,visit:!1,wait:!1},protractor:{$:!1,$$:!1,browser:!1,by:!1,By:!1,DartObject:!1,element:!1,protractor:!1},"shared-node-browser":{clearInterval:!1,clearTimeout:!1,console:!1,setInterval:!1,setTimeout:!1,URL:!1,URLSearchParams:!1},webextensions:{browser:!1,chrome:!1,opr:!1},greasemonkey:{cloneInto:!1,createObjectIn:!1,exportFunction:!1,GM:!1,GM_addStyle:!1,GM_deleteValue:!1,GM_getResourceText:!1,GM_getResourceURL:!1,GM_getValue:!1,GM_info:!1,GM_listValues:!1,GM_log:!1,GM_openInTab:!1,GM_registerMenuCommand:!1,GM_setClipboard:!1,GM_setValue:!1,GM_xmlhttpRequest:!1,unsafeWindow:!1},devtools:{$:!1,$_:!1,$$:!1,$0:!1,$1:!1,$2:!1,$3:!1,$4:!1,$x:!1,chrome:!1,clear:!1,copy:!1,debug:!1,dir:!1,dirxml:!1,getEventListeners:!1,inspect:!1,keys:!1,monitor:!1,monitorEvents:!1,profile:!1,profileEnd:!1,queryObjects:!1,table:!1,undebug:!1,unmonitor:!1,unmonitorEvents:!1,values:!1}};function Wp(){return Up?Fp:(Up=1,Fp=qp)}var Gp=(void Tr.env.BABEL_8_BREAKING,Wp()),Vp=Pa,Hp=Yn,Kp=Hc,zp=gu,Xp=cs,Jp=P,Yp=kt,$p=qt,Qp=le,Zp=pe,ef=fe,tf=ge,rf=ye,af=M,nf=F,sf=ve,of=Mt,df=Lt,cf=Vt,lf=V,uf=Y,pf=Ft,ff=Nt,gf=K,yf=Te,mf=Pe,hf=Ae,vf=re,bf=ae,Rf=se,xf=$t,jf=vs,Ef=fs,wf=eu,Sf=Ns,Tf=Bs,Pf=pt,Af=ft,kf=Q,Cf=gt,_f=je,If=Me,Df=Gt,Of=qc;function Nf(e,t){switch(null==e?void 0:e.type){default:var r;if(sf(e)||Df(e))if((ef(e)||rf(e)||sf(e))&&e.source)Nf(e.source,t);else if((rf(e)||sf(e))&&null!=(r=e.specifiers)&&r.length)for(var a,n=j(e.specifiers);!(a=n()).done;){Nf(a.value,t)}else(tf(e)||rf(e))&&e.declaration&&Nf(e.declaration,t);else cf(e)?Nf(e.local,t):!of(e)||lf(e)||gf(e)||hf(e)||t.push(e.value);break;case"MemberExpression":case"OptionalMemberExpression":case"JSXMemberExpression":Nf(e.object,t),Nf(e.property,t);break;case"Identifier":case"JSXIdentifier":t.push(e.name);break;case"CallExpression":case"OptionalCallExpression":case"NewExpression":Nf(e.callee,t);break;case"ObjectExpression":case"ObjectPattern":for(var s,i=j(e.properties);!(s=i()).done;){Nf(s.value,t)}break;case"SpreadElement":case"RestElement":case"UnaryExpression":case"UpdateExpression":Nf(e.argument,t);break;case"ObjectProperty":case"ObjectMethod":case"ClassProperty":case"ClassMethod":case"ClassPrivateProperty":case"ClassPrivateMethod":Nf(e.key,t);break;case"ThisExpression":t.push("this");break;case"Super":t.push("super");break;case"Import":t.push("import");break;case"DoExpression":t.push("do");break;case"YieldExpression":t.push("yield"),Nf(e.argument,t);break;case"AwaitExpression":t.push("await"),Nf(e.argument,t);break;case"AssignmentExpression":Nf(e.left,t);break;case"VariableDeclarator":case"FunctionExpression":case"FunctionDeclaration":case"ClassExpression":case"ClassDeclaration":case"PrivateName":Nf(e.id,t);break;case"ParenthesizedExpression":Nf(e.expression,t);break;case"MetaProperty":Nf(e.meta,t),Nf(e.property,t);break;case"JSXElement":Nf(e.openingElement,t);break;case"JSXOpeningElement":Nf(e.name,t);break;case"JSXFragment":Nf(e.openingFragment,t);break;case"JSXOpeningFragment":t.push("Fragment");break;case"JSXNamespacedName":Nf(e.namespace,t),Nf(e.name,t)}}var Bf={ForStatement:function(e){var t=e.get("init");if(t.isVar()){var r=e.scope;(r.getFunctionParent()||r.getProgramParent()).registerBinding("var",t)}},Declaration:function(e){e.isBlockScoped()||(e.isImportDeclaration()||e.isExportDeclaration()||(e.scope.getFunctionParent()||e.scope.getProgramParent()).registerDeclaration(e))},ImportDeclaration:function(e){e.scope.getBlockParent().registerDeclaration(e)},ReferencedIdentifier:function(e,t){t.references.push(e)},ForXStatement:function(e,t){var r=e.get("left");if(r.isPattern()||r.isIdentifier())t.constantViolations.push(e);else if(r.isVar()){var a=e.scope;(a.getFunctionParent()||a.getProgramParent()).registerBinding("var",r)}},ExportDeclaration:{exit:function(e){var t=e.node,r=e.scope;if(!ef(t)){var a=t.declaration;if(Zp(a)||af(a)){var n=a.id;if(!n)return;var s=r.getBinding(n.name);null==s||s.reference(e)}else if(Rf(a))for(var i,o=j(a.declarations);!(i=o()).done;)for(var d=i.value,c=0,l=Object.keys(zp(d));c1&&(r+=t),"_"+r},t.generateUidBasedOnNode=function(e,t){var r=[];Nf(e,r);var a=r.join("$");return a=a.replace(/^_/,"")||t||"ref",this.generateUid(a.slice(0,20))},t.generateUidIdentifierBasedOnNode=function(e,t){return Xp(this.generateUidBasedOnNode(e,t))},t.isStatic=function(e){if(vf(e)||yf(e)||Cf(e))return!0;if(nf(e)){var t=this.getBinding(e.name);return t?t.constant:this.hasBinding(e.name)}return!1},t.maybeGenerateMemoised=function(e,t){if(this.isStatic(e))return null;var r=this.generateUidIdentifierBasedOnNode(e);return t?r:(this.push({id:r}),Kp(r))},t.checkBlockScopedCollisions=function(e,t,r,a){if("param"!==t&&("local"!==e.kind&&("let"===t||"let"===e.kind||"const"===e.kind||"module"===e.kind||"param"===e.kind&&"const"===t)))throw this.hub.buildError(a,'Duplicate declaration "'+r+'"',TypeError)},t.rename=function(e,t){var r=this.getBinding(e);r&&(t||(t=this.generateUidIdentifier(e).name),new Mp(r,e,t).rename(arguments[2]))},t._renameFromMap=function(e,t,r,a){e[t]&&(e[r]=a,e[t]=null)},t.dump=function(){var e="-".repeat(60);console.log(e);var t=this;do{console.log("#",t.block.type);for(var r=0,a=Object.keys(t.bindings);r0)&&this.isPure(e.body,t));if(Qp(e)){for(var i,o=j(e.body);!(i=o()).done;){var d=i.value;if(!this.isPure(d,t))return!1}return!0}if(Yp(e))return this.isPure(e.left,t)&&this.isPure(e.right,t);if(Jp(e)||Af(e)){for(var c,l=j(e.elements);!(c=l()).done;){var u=c.value;if(null!==u&&!this.isPure(u,t))return!1}return!0}if(uf(e)||Pf(e)){for(var p,f=j(e.properties);!(p=f()).done;){var g=p.value;if(!this.isPure(g,t))return!1}return!0}if(df(e))return!(e.computed&&!this.isPure(e.key,t))&&!((null==(n=e.decorators)?void 0:n.length)>0);if(pf(e))return!(e.computed&&!this.isPure(e.key,t))&&(!((null==(s=e.decorators)?void 0:s.length)>0)&&!((kf(e)||e.static)&&null!==e.value&&!this.isPure(e.value,t)));if(bf(e))return this.isPure(e.argument,t);if(mf(e))return xf(e.tag,"String.raw")&&!this.hasBinding("String",!0)&&this.isPure(e.quasi,t);if(hf(e)){for(var y,m=j(e.expressions);!(y=m()).done;){var h=y.value;if(!this.isPure(h,t))return!1}return!0}return ff(e)},t.setData=function(e,t){return this.data[e]=t},t.getData=function(e){var t=this;do{var r=t.data[e];if(null!=r)return r}while(t=t.parent)},t.removeData=function(e){var t=this;do{null!=t.data[e]&&(t.data[e]=null)}while(t=t.parent)},t.init=function(){this.inited||(this.inited=!0,this.crawl())},t.crawl=function(){var e=this.path;this.references=Object.create(null),this.bindings=Object.create(null),this.globals=Object.create(null),this.uids=Object.create(null),this.data=Object.create(null);var t=this.getProgramParent();if(!t.crawling){var r={references:[],constantViolations:[],assignments:[]};if(this.crawling=!0,"Program"!==e.type&&rp(Bf)){for(var a,n=j(Bf.enter);!(a=n()).done;){a.value.call(r,e,r)}var s=Bf[e.type];if(s)for(var i,o=j(s.enter);!(i=o()).done;){i.value.call(r,e,r)}}e.traverse(Bf,r),this.crawling=!1;for(var d,c=j(r.assignments);!(d=c()).done;){for(var l=d.value,u=l.getBindingIdentifiers(),p=0,f=Object.keys(u);p>18&63]+Gf[n>>12&63]+Gf[n>>6&63]+Gf[63&n]);return s.join("")}function Jf(e){var t;Kf||zf();for(var r=e.length,a=r%3,n="",s=[],i=16383,o=0,d=r-a;od?d:o+i));return 1===a?(t=e[r-1],n+=Gf[t>>2],n+=Gf[t<<4&63],n+="=="):2===a&&(t=(e[r-2]<<8)+e[r-1],n+=Gf[t>>10],n+=Gf[t>>4&63],n+=Gf[t<<2&63],n+="="),s.push(n),s.join("")}function Yf(e,t,r,a,n){var s,i,o=8*n-a-1,d=(1<>1,l=-7,u=r?n-1:0,p=r?-1:1,f=e[t+u];for(u+=p,s=f&(1<<-l)-1,f>>=-l,l+=o;l>0;s=256*s+e[t+u],u+=p,l-=8);for(i=s&(1<<-l)-1,s>>=-l,l+=a;l>0;i=256*i+e[t+u],u+=p,l-=8);if(0===s)s=1-c;else{if(s===d)return i?NaN:1/0*(f?-1:1);i+=Math.pow(2,a),s-=c}return(f?-1:1)*i*Math.pow(2,s-a)}function $f(e,t,r,a,n,s){var i,o,d,c=8*s-n-1,l=(1<>1,p=23===n?Math.pow(2,-24)-Math.pow(2,-77):0,f=a?0:s-1,g=a?1:-1,y=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(o=isNaN(t)?1:0,i=l):(i=Math.floor(Math.log(t)/Math.LN2),t*(d=Math.pow(2,-i))<1&&(i--,d*=2),(t+=i+u>=1?p/d:p*Math.pow(2,1-u))*d>=2&&(i++,d/=2),i+u>=l?(o=0,i=l):i+u>=1?(o=(t*d-1)*Math.pow(2,n),i+=u):(o=t*Math.pow(2,u-1)*Math.pow(2,n),i=0));n>=8;e[r+f]=255&o,f+=g,o/=256,n-=8);for(i=i<0;e[r+f]=255&i,f+=g,i/=256,c-=8);e[r+f-g]|=128*y}var Qf={}.toString,Zf=Array.isArray||function(e){return"[object Array]"==Qf.call(e)};function eg(){return rg.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function tg(e,t){if(eg()=eg())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+eg().toString(16)+" bytes");return 0|e}function dg(e){return!(null==e||!e._isBuffer)}function cg(e,t){if(dg(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var r=e.length;if(0===r)return 0;for(var a=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return Bg(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return Mg(e).length;default:if(a)return Bg(e).length;t=(""+t).toLowerCase(),a=!0}}function lg(e,t,r){var a=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return Sg(this,t,r);case"utf8":case"utf-8":return xg(this,t,r);case"ascii":return Eg(this,t,r);case"latin1":case"binary":return wg(this,t,r);case"base64":return Rg(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return Tg(this,t,r);default:if(a)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),a=!0}}function ug(e,t,r){var a=e[t];e[t]=e[r],e[r]=a}function pg(e,t,r,a,n){if(0===e.length)return-1;if("string"==typeof r?(a=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),r=+r,isNaN(r)&&(r=n?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(n)return-1;r=e.length-1}else if(r<0){if(!n)return-1;r=0}if("string"==typeof t&&(t=rg.from(t,a)),dg(t))return 0===t.length?-1:fg(e,t,r,a,n);if("number"==typeof t)return t&=255,rg.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?n?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):fg(e,[t],r,a,n);throw new TypeError("val must be string, number or Buffer")}function fg(e,t,r,a,n){var s,i=1,o=e.length,d=t.length;if(void 0!==a&&("ucs2"===(a=String(a).toLowerCase())||"ucs-2"===a||"utf16le"===a||"utf-16le"===a)){if(e.length<2||t.length<2)return-1;i=2,o/=2,d/=2,r/=2}function c(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(n){var l=-1;for(s=r;so&&(r=o-d),s=r;s>=0;s--){for(var u=!0,p=0;pn&&(a=n):a=n;var s=t.length;if(s%2!=0)throw new TypeError("Invalid hex string");a>s/2&&(a=s/2);for(var i=0;i>8,n=r%256,s.push(n),s.push(a);return s}(t,e.length-r),e,r,a)}function Rg(e,t,r){return 0===t&&r===e.length?Jf(e):Jf(e.slice(t,r))}function xg(e,t,r){r=Math.min(e.length,r);for(var a=[],n=t;n239?4:c>223?3:c>191?2:1;if(n+u<=r)switch(u){case 1:c<128&&(l=c);break;case 2:128==(192&(s=e[n+1]))&&(d=(31&c)<<6|63&s)>127&&(l=d);break;case 3:s=e[n+1],i=e[n+2],128==(192&s)&&128==(192&i)&&(d=(15&c)<<12|(63&s)<<6|63&i)>2047&&(d<55296||d>57343)&&(l=d);break;case 4:s=e[n+1],i=e[n+2],o=e[n+3],128==(192&s)&&128==(192&i)&&128==(192&o)&&(d=(15&c)<<18|(63&s)<<12|(63&i)<<6|63&o)>65535&&d<1114112&&(l=d)}null===l?(l=65533,u=1):l>65535&&(l-=65536,a.push(l>>>10&1023|55296),l=56320|1023&l),a.push(l),n+=u}return function(e){var t=e.length;if(t<=jg)return String.fromCharCode.apply(String,e);var r="",a=0;for(;a0&&(e=this.toString("hex",0,50).match(/.{2}/g).join(" "),this.length>50&&(e+=" ... ")),""},rg.prototype.compare=function(e,t,r,a,n){if(!dg(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===a&&(a=0),void 0===n&&(n=this.length),t<0||r>e.length||a<0||n>this.length)throw new RangeError("out of range index");if(a>=n&&t>=r)return 0;if(a>=n)return-1;if(t>=r)return 1;if(this===e)return 0;for(var s=(n>>>=0)-(a>>>=0),i=(r>>>=0)-(t>>>=0),o=Math.min(s,i),d=this.slice(a,n),c=e.slice(t,r),l=0;ln)&&(r=n),e.length>0&&(r<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");a||(a="utf8");for(var s=!1;;)switch(a){case"hex":return gg(this,e,t,r);case"utf8":case"utf-8":return yg(this,e,t,r);case"ascii":return mg(this,e,t,r);case"latin1":case"binary":return hg(this,e,t,r);case"base64":return vg(this,e,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return bg(this,e,t,r);default:if(s)throw new TypeError("Unknown encoding: "+a);a=(""+a).toLowerCase(),s=!0}},rg.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var jg=4096;function Eg(e,t,r){var a="";r=Math.min(e.length,r);for(var n=t;na)&&(r=a);for(var n="",s=t;sr)throw new RangeError("Trying to access beyond buffer length")}function Ag(e,t,r,a,n,s){if(!dg(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>n||te.length)throw new RangeError("Index out of range")}function kg(e,t,r,a){t<0&&(t=65535+t+1);for(var n=0,s=Math.min(e.length-r,2);n>>8*(a?n:1-n)}function Cg(e,t,r,a){t<0&&(t=4294967295+t+1);for(var n=0,s=Math.min(e.length-r,4);n>>8*(a?n:3-n)&255}function _g(e,t,r,a,n,s){if(r+a>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function Ig(e,t,r,a,n){return n||_g(e,0,r,4),$f(e,t,r,a,23,4),r+4}function Dg(e,t,r,a,n){return n||_g(e,0,r,8),$f(e,t,r,a,52,8),r+8}rg.prototype.slice=function(e,t){var r,a=this.length;if((e=~~e)<0?(e+=a)<0&&(e=0):e>a&&(e=a),(t=void 0===t?a:~~t)<0?(t+=a)<0&&(t=0):t>a&&(t=a),t0&&(n*=256);)a+=this[e+--t]*n;return a},rg.prototype.readUInt8=function(e,t){return t||Pg(e,1,this.length),this[e]},rg.prototype.readUInt16LE=function(e,t){return t||Pg(e,2,this.length),this[e]|this[e+1]<<8},rg.prototype.readUInt16BE=function(e,t){return t||Pg(e,2,this.length),this[e]<<8|this[e+1]},rg.prototype.readUInt32LE=function(e,t){return t||Pg(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},rg.prototype.readUInt32BE=function(e,t){return t||Pg(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},rg.prototype.readIntLE=function(e,t,r){e|=0,t|=0,r||Pg(e,t,this.length);for(var a=this[e],n=1,s=0;++s=(n*=128)&&(a-=Math.pow(2,8*t)),a},rg.prototype.readIntBE=function(e,t,r){e|=0,t|=0,r||Pg(e,t,this.length);for(var a=t,n=1,s=this[e+--a];a>0&&(n*=256);)s+=this[e+--a]*n;return s>=(n*=128)&&(s-=Math.pow(2,8*t)),s},rg.prototype.readInt8=function(e,t){return t||Pg(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},rg.prototype.readInt16LE=function(e,t){t||Pg(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},rg.prototype.readInt16BE=function(e,t){t||Pg(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},rg.prototype.readInt32LE=function(e,t){return t||Pg(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},rg.prototype.readInt32BE=function(e,t){return t||Pg(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},rg.prototype.readFloatLE=function(e,t){return t||Pg(e,4,this.length),Yf(this,e,!0,23,4)},rg.prototype.readFloatBE=function(e,t){return t||Pg(e,4,this.length),Yf(this,e,!1,23,4)},rg.prototype.readDoubleLE=function(e,t){return t||Pg(e,8,this.length),Yf(this,e,!0,52,8)},rg.prototype.readDoubleBE=function(e,t){return t||Pg(e,8,this.length),Yf(this,e,!1,52,8)},rg.prototype.writeUIntLE=function(e,t,r,a){(e=+e,t|=0,r|=0,a)||Ag(this,e,t,r,Math.pow(2,8*r)-1,0);var n=1,s=0;for(this[t]=255&e;++s=0&&(s*=256);)this[t+n]=e/s&255;return t+r},rg.prototype.writeUInt8=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,1,255,0),rg.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},rg.prototype.writeUInt16LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,65535,0),rg.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):kg(this,e,t,!0),t+2},rg.prototype.writeUInt16BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,65535,0),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):kg(this,e,t,!1),t+2},rg.prototype.writeUInt32LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,4294967295,0),rg.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):Cg(this,e,t,!0),t+4},rg.prototype.writeUInt32BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,4294967295,0),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):Cg(this,e,t,!1),t+4},rg.prototype.writeIntLE=function(e,t,r,a){if(e=+e,t|=0,!a){var n=Math.pow(2,8*r-1);Ag(this,e,t,r,n-1,-n)}var s=0,i=1,o=0;for(this[t]=255&e;++s>0)-o&255;return t+r},rg.prototype.writeIntBE=function(e,t,r,a){if(e=+e,t|=0,!a){var n=Math.pow(2,8*r-1);Ag(this,e,t,r,n-1,-n)}var s=r-1,i=1,o=0;for(this[t+s]=255&e;--s>=0&&(i*=256);)e<0&&0===o&&0!==this[t+s+1]&&(o=1),this[t+s]=(e/i>>0)-o&255;return t+r},rg.prototype.writeInt8=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,1,127,-128),rg.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},rg.prototype.writeInt16LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,32767,-32768),rg.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):kg(this,e,t,!0),t+2},rg.prototype.writeInt16BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,32767,-32768),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):kg(this,e,t,!1),t+2},rg.prototype.writeInt32LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,2147483647,-2147483648),rg.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):Cg(this,e,t,!0),t+4},rg.prototype.writeInt32BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):Cg(this,e,t,!1),t+4},rg.prototype.writeFloatLE=function(e,t,r){return Ig(this,e,t,!0,r)},rg.prototype.writeFloatBE=function(e,t,r){return Ig(this,e,t,!1,r)},rg.prototype.writeDoubleLE=function(e,t,r){return Dg(this,e,t,!0,r)},rg.prototype.writeDoubleBE=function(e,t,r){return Dg(this,e,t,!1,r)},rg.prototype.copy=function(e,t,r,a){if(r||(r=0),a||0===a||(a=this.length),t>=e.length&&(t=e.length),t||(t=0),a>0&&a=this.length)throw new RangeError("sourceStart out of bounds");if(a<0)throw new RangeError("sourceEnd out of bounds");a>this.length&&(a=this.length),e.length-t=0;--n)e[n+t]=this[n+r];else if(s<1e3||!rg.TYPED_ARRAY_SUPPORT)for(n=0;n>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(s=t;s55295&&r<57344){if(!n){if(r>56319){(t-=3)>-1&&s.push(239,191,189);continue}if(i+1===a){(t-=3)>-1&&s.push(239,191,189);continue}n=r;continue}if(r<56320){(t-=3)>-1&&s.push(239,191,189),n=r;continue}r=65536+(n-55296<<10|r-56320)}else n&&(t-=3)>-1&&s.push(239,191,189);if(n=null,r<128){if((t-=1)<0)break;s.push(r)}else if(r<2048){if((t-=2)<0)break;s.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;s.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;s.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return s}function Mg(e){return function(e){var t,r,a,n,s,i;Kf||zf();var o=e.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");s="="===e[o-2]?2:"="===e[o-1]?1:0,i=new Hf(3*o/4-s),a=s>0?o-4:o;var d=0;for(t=0,r=0;t>16&255,i[d++]=n>>8&255,i[d++]=255&n;return 2===s?(n=Vf[e.charCodeAt(t)]<<2|Vf[e.charCodeAt(t+1)]>>4,i[d++]=255&n):1===s&&(n=Vf[e.charCodeAt(t)]<<10|Vf[e.charCodeAt(t+1)]<<4|Vf[e.charCodeAt(t+2)]>>2,i[d++]=n>>8&255,i[d++]=255&n),i}(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(Og,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function Lg(e,t,r,a){for(var n=0;n=t.length||n>=e.length);++n)t[n+r]=e[n];return n}function Fg(e){return null!=e&&(!!e._isBuffer||Ug(e)||function(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&Ug(e.slice(0,0))}(e))}function Ug(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}var qg,Wg={exports:{}};function Gg(){return qg||(qg=1,function(e,t){!function(e){for(var t=",".charCodeAt(0),r=";".charCodeAt(0),a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(64),s=new Uint8Array(128),i=0;i>>=1,c&&(n=-2147483648|-n),r[a]+=n,t}function p(e,r,a){return!(r>=a)&&e.charCodeAt(r)!==t}function f(e){e.sort(g)}function g(e,t){return e[0]-t[0]}function y(e){for(var a=new Int32Array(5),n=16384,s=n-36,i=new Uint8Array(n),o=i.subarray(0,s),c=0,l="",u=0;u0&&(c===n&&(l+=d.decode(i),c=0),i[c++]=r),0!==p.length){a[0]=0;for(var f=0;fs&&(l+=d.decode(o),i.copyWithin(0,s,c),c-=s),f>0&&(i[c++]=t),c=m(i,c,a,g,0),1!==g.length&&(c=m(i,c,a,g,1),c=m(i,c,a,g,2),c=m(i,c,a,g,3),4!==g.length&&(c=m(i,c,a,g,4)))}}}return l+d.decode(i.subarray(0,c))}function m(e,t,r,a,s){var i=a[s],o=i-r[s];r[s]=i,o=o<0?-o<<1|1:o<<1;do{var d=31&o;(o>>>=5)>0&&(d|=32),e[t++]=n[d]}while(o>0);return t}e.decode=c,e.encode=y,Object.defineProperty(e,"__esModule",{value:!0})}(t)}(0,Wg.exports)),Wg.exports}var Vg,Hg={exports:{}},Kg={exports:{}};function zg(){return Vg||(Vg=1,function(e,t){!function(e){for(var t=",".charCodeAt(0),r=";".charCodeAt(0),a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(64),s=new Uint8Array(128),i=0;i>>=1,c&&(n=-2147483648|-n),r[a]+=n,t}function p(e,r,a){return!(r>=a)&&e.charCodeAt(r)!==t}function f(e){e.sort(g)}function g(e,t){return e[0]-t[0]}function y(e){for(var a=new Int32Array(5),n=16384,s=n-36,i=new Uint8Array(n),o=i.subarray(0,s),c=0,l="",u=0;u0&&(c===n&&(l+=d.decode(i),c=0),i[c++]=r),0!==p.length){a[0]=0;for(var f=0;fs&&(l+=d.decode(o),i.copyWithin(0,s,c),c-=s),f>0&&(i[c++]=t),c=m(i,c,a,g,0),1!==g.length&&(c=m(i,c,a,g,1),c=m(i,c,a,g,2),c=m(i,c,a,g,3),4!==g.length&&(c=m(i,c,a,g,4)))}}}return l+d.decode(i.subarray(0,c))}function m(e,t,r,a,s){var i=a[s],o=i-r[s];r[s]=i,o=o<0?-o<<1|1:o<<1;do{var d=31&o;(o>>>=5)>0&&(d|=32),e[t++]=n[d]}while(o>0);return t}e.decode=c,e.encode=y,Object.defineProperty(e,"__esModule",{value:!0})}(t)}(0,Kg.exports)),Kg.exports}var Xg,Jg={exports:{}};function Yg(){return Xg||(Xg=1,function(e,t){e.exports=function(){var e,t=/^[\w+.-]+:\/\//,r=/^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/,a=/^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i;function n(e){return t.test(e)}function s(e){return e.startsWith("//")}function i(e){return e.startsWith("/")}function o(e){return e.startsWith("file:")}function d(e){return/^[.?#]/.test(e)}function c(e){var t=r.exec(e);return u(t[1],t[2]||"",t[3],t[4]||"",t[5]||"/",t[6]||"",t[7]||"")}function l(e){var t=a.exec(e),r=t[2];return u("file:","",t[1]||"","",i(r)?r:"/"+r,t[3]||"",t[4]||"")}function u(t,r,a,n,s,i,o){return{scheme:t,user:r,host:a,port:n,path:s,query:i,hash:o,type:e.Absolute}}function p(t){if(s(t)){var r=c("http:"+t);return r.scheme="",r.type=e.SchemeRelative,r}if(i(t)){var a=c("http://foo.com"+t);return a.scheme="",a.host="",a.type=e.AbsolutePath,a}if(o(t))return l(t);if(n(t))return c(t);var d=c("http://foo.com/"+t);return d.scheme="",d.host="",d.type=t?t.startsWith("?")?e.Query:t.startsWith("#")?e.Hash:e.RelativePath:e.Empty,d}function f(e){if(e.endsWith("/.."))return e;var t=e.lastIndexOf("/");return e.slice(0,t+1)}function g(e,t){y(t,t.type),"/"===e.path?e.path=t.path:e.path=f(t.path)+e.path}function y(t,r){for(var a=r<=e.RelativePath,n=t.path.split("/"),s=1,i=0,o=!1,d=1;dn&&(n=i)}y(a,n);var o=a.query+a.hash;switch(n){case e.Hash:case e.Query:return o;case e.RelativePath:var c=a.path.slice(1);return c?d(r||t)&&!d(c)?"./"+c+o:c+o:o||".";case e.AbsolutePath:return a.path+o;default:return a.scheme+"//"+a.user+a.host+a.port+a.path+o}}return function(e){e[e.Empty=1]="Empty",e[e.Hash=2]="Hash",e[e.Query=3]="Query",e[e.RelativePath=4]="RelativePath",e[e.AbsolutePath=5]="AbsolutePath",e[e.SchemeRelative=6]="SchemeRelative",e[e.Absolute=7]="Absolute"}(e||(e={})),m}()}(Jg)),Jg.exports}!function(e,t){!function(e,t,r){function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=a(r);function s(e,t){return t&&!t.endsWith("/")&&(t+="/"),n.default(e,t)}function i(e){if(!e)return"";var t=e.lastIndexOf("/");return e.slice(0,t+1)}var o=0,c=1,l=2,u=3,p=4,f=1,g=2;function y(e,t){var r=m(e,0);if(r===e.length)return e;t||(e=e.slice());for(var a=r;a>1),s=e[n][o]-t;if(0===s)return R=!0,n;s<0?r=n+1:a=n-1}return R=!1,r-1}function j(e,t,r){for(var a=r+1;a=0&&e[a][o]===t;r=a--);return r}function w(){return{lastKey:-1,lastNeedle:-1,lastIndex:-1}}function S(e,t,r,a){var n=r.lastKey,s=r.lastNeedle,i=r.lastIndex,d=0,c=e.length-1;if(a===n){if(t===s)return R=-1!==i&&e[i][o]===t,i;t>=s?d=-1===i?0:i:c=i}return r.lastKey=a,r.lastNeedle=t,r.lastIndex=x(e,t,d,c)}function T(e,t){for(var r=t.map(A),a=0;at;a--)e[a]=e[a-1];e[t]=r}function A(){return{__proto__:null}}var k=function(t,r){var a="string"==typeof t?JSON.parse(t):t;if(!("sections"in a))return new L(a,r);var n=[],s=[],i=[],o=[];C(a,r,n,s,i,o,0,0,1/0,1/0);var d={version:3,file:a.file,names:o,sources:s,sourcesContent:i,mappings:n};return e.presortedDecodedMap(d)};function C(e,t,r,a,n,s,i,o,d,c){for(var l=e.sections,u=0;ug)return;for(var S=D(a,w),T=0===E?f:0,P=b[E],A=0;A=y)return;if(1!==k.length){var O=h+k[c],N=k[l],B=k[u];S.push(4===k.length?[_,O,N,B]:[_,O,N,B,v+k[p]])}else S.push([_])}}}function I(e,t){for(var r=0;r=n.length)return null;var s=n[r],i=W(s,t._decodedMemo,r,a,M);return-1===i?null:s[i]},e.originalPositionFor=function(t,r){var a=r.line,n=r.column,s=r.bias;if(--a<0)throw new Error(O);if(n<0)throw new Error(N);var i=e.decodedMappings(t);if(a>=i.length)return U(null,null,null,null);var o=i[a],d=W(o,t._decodedMemo,a,n,s||M);if(-1===d)return U(null,null,null,null);var f=o[d];if(1===f.length)return U(null,null,null,null);var g=t.names;return U(t.resolvedSources[f[c]],f[l]+1,f[u],5===f.length?g[f[p]]:null)},e.allGeneratedPositionsFor=function(e,t){return r(e,t.source,t.line,t.column,t.bias||B,!0)},e.generatedPositionFor=function(e,t){return r(e,t.source,t.line,t.column,t.bias||M,!1)},e.eachMapping=function(t,r){for(var a=e.decodedMappings(t),n=t.names,s=t.resolvedSources,i=0;i=0&&!(t>=e[a][s]);r=a--);return r}function y(e,t,r){for(var a=e.length;a>t;a--)e[a]=e[a-1];e[t]=r}function m(e){for(var t=e.length,r=t,a=r-1;a>=0&&!(e[a].length>0);r=a,a--);r1?this._indentChar.repeat(t):this._indentChar}else this._str+=t>1?String.fromCharCode(e).repeat(t):String.fromCharCode(e);10!==e?(this._mark(r.line,r.column,r.identifierName,r.identifierNamePos,r.filename),this._position.column+=t):(this._position.line++,this._position.column=0),this._canMarkIdName&&(r.identifierName=void 0,r.identifierNamePos=void 0)},t._append=function(e,t,r){var a=e.length,n=this._position;if(this._last=e.charCodeAt(a-1),++this._appendCount>4096?(this._str,this._buf+=this._str,this._str=e,this._appendCount=0):this._str+=e,r||this._map){var s=t.column,i=t.identifierName,o=t.identifierNamePos,d=t.filename,c=t.line;null==i&&null==o||!this._canMarkIdName||(t.identifierName=void 0,t.identifierNamePos=void 0);var l=e.indexOf("\n"),u=0;for(0!==l&&this._mark(c,s,i,o,d);-1!==l;)n.line++,n.column=0,(u=l+1)=0&&10===this._queue[r].char;r--)t++;return t===e&&10===this._last?t+1:t},t.endsWithCharAndNewline=function(){var e=this._queue,t=this._queueCursor;if(0!==t){if(10!==e[t-1].char)return;return t>1?e[t-2].char:this._last}},t.hasContent=function(){return 0!==this._queueCursor||!!this._last},t.exactSource=function(e,t){if(this._map){this.source("start",e);var r=e.identifierName,a=this._sourcePosition;r&&(this._canMarkIdName=!1,a.identifierName=r),t(),r&&(this._canMarkIdName=!0,a.identifierName=void 0,a.identifierNamePos=void 0),this.source("end",e)}else t()},t.source=function(e,t){this._map&&this._normalizePosition(e,t,0)},t.sourceWithOffset=function(e,t,r){this._map&&this._normalizePosition(e,t,r)},t.withSource=function(e,t,r){this._map&&this.source(e,t),r()},t._normalizePosition=function(e,t,r){var a=t[e],n=this._sourcePosition;a&&(n.line=a.line,n.column=Math.max(a.column+r,0),n.filename=t.filename)},t.getCurrentColumn=function(){for(var e=this._queue,t=this._queueCursor,r=-1,a=0,n=0;n",0],["&&",1],["|",2],["^",3],["&",4],["==",5],["===",5],["!=",5],["!==",5],["<",6],[">",6],["<=",6],[">=",6],["in",6],["instanceof",6],[">>",7],["<<",7],[">>>",7],["+",8],["-",8],["*",9],["/",9],["%",9],["**",10]]);function Iy(e){return"TSAsExpression"===e||"TSSatisfiesExpression"===e||"TSTypeAssertion"===e}var Dy=function(e,t){var r=t.type;return("ClassDeclaration"===r||"ClassExpression"===r)&&t.superClass===e},Oy=function(e,t){var r=t.type;return("MemberExpression"===r||"OptionalMemberExpression"===r)&&t.object===e||("CallExpression"===r||"OptionalCallExpression"===r||"NewExpression"===r)&&t.callee===e||"TaggedTemplateExpression"===r&&t.tag===e||"TSNonNullExpression"===r};function Ny(e,t){var r=t.type;return"ArrayTypeAnnotation"===r||"NullableTypeAnnotation"===r||"IntersectionTypeAnnotation"===r||"UnionTypeAnnotation"===r}function By(){return!0}function My(e,t){var r=t.type;return"TSArrayType"===r||"TSOptionalType"===r||"TSIntersectionType"===r||"TSUnionType"===r||"TSRestType"===r}function Ly(e,t){var r=t.type;return"BinaryExpression"===r||"LogicalExpression"===r||"UnaryExpression"===r||"SpreadElement"===r||Oy(e,t)||"AwaitExpression"===r&&Cy(e)||"ConditionalExpression"===r&&e===t.test||Dy(e,t)}function Fy(e,t){return Oy(e,t)||jy(t)&&"**"===t.operator&&t.left===e||Dy(e,t)}function Uy(e,t){var r=t.type;return!!("UnaryExpression"===r||"SpreadElement"===r||"BinaryExpression"===r||"LogicalExpression"===r||"ConditionalExpression"===r&&t.test===e||"AwaitExpression"===r||Iy(r))||Fy(e,t)}function qy(e,t){return Ey(t)&&t.callee===e||Py(t)&&t.object===e}function Wy(e,t){var r=1&t,a=2&t,n=4&t,s=8&t,i=16&t,o=32&t,d=e.length-1;if(!(d<=0)){for(var c=e[d],l=e[--d];d>=0;){var u=l.type;if(r&&"ExpressionStatement"===u&&l.expression===c||n&&"ExportDefaultDeclaration"===u&&c===l.declaration||a&&"ArrowFunctionExpression"===u&&l.body===c||s&&"ForStatement"===u&&l.init===c||i&&"ForInStatement"===u&&l.left===c||o&&"ForOfStatement"===u&&l.left===c)return!0;if(!(d>0&&(Oy(c,l)&&"NewExpression"!==u||"SequenceExpression"===u&&l.expressions[0]===c||"UpdateExpression"===u&&!l.prefix||"ConditionalExpression"===u&&l.test===c||("BinaryExpression"===u||"LogicalExpression"===u)&&l.left===c||"AssignmentExpression"===u&&l.left===c)))return!1;c=l,l=e[--d]}return!1}}var Gy=Object.freeze({__proto__:null,ArrowFunctionExpression:function(e,t){return wy(t)||Uy(e,t)},AssignmentExpression:function(e,t){return!!Ay(e.left)||Uy(e,t)},AwaitExpression:Ly,Binary:function(e,t){var r=t.type;if("**"===e.operator&&"BinaryExpression"===r&&"**"===t.operator)return t.left===e;if(Dy(e,t))return!0;if(Oy(e,t)||"UnaryExpression"===r||"SpreadElement"===r||"AwaitExpression"===r)return!0;if("BinaryExpression"===r||"LogicalExpression"===r){var a=_y.get(t.operator),n=_y.get(e.operator);if(a===n&&t.right===e&&"LogicalExpression"!==r||a>n)return!0}},BinaryExpression:function(e,t){if("in"===e.operator){var r=t.type;return"VariableDeclarator"===r||"ForStatement"===r||"ForInStatement"===r||"ForOfStatement"===r}return!1},ClassExpression:function(e,t,r){return Wy(r,5)},ConditionalExpression:Uy,DoExpression:function(e,t,r){return!e.async&&Wy(r,1)},FunctionExpression:function(e,t,r){return Wy(r,5)},FunctionTypeAnnotation:function(e,t,r){if(!(r.length<3)){var a=t.type;return"UnionTypeAnnotation"===a||"IntersectionTypeAnnotation"===a||"ArrayTypeAnnotation"===a||"TypeAnnotation"===a&&xy(r[r.length-3])}},Identifier:function(e,t,r){var a,n=t.type;if(null!=(a=e.extra)&&a.parenthesized&&"AssignmentExpression"===n&&t.left===e){var s=t.right.type;if(("FunctionExpression"===s||"ClassExpression"===s)&&null==t.right.id)return!0}return"let"===e.name?Wy(r,Py(t,{object:e,computed:!0})||ky(t,{object:e,computed:!0,optional:!1})?57:32):"async"===e.name&&Sy(t)&&e===t.left},IntersectionTypeAnnotation:Ny,LogicalExpression:function(e,t){var r=t.type;if(Iy(r))return!0;if("LogicalExpression"!==r)return!1;switch(e.operator){case"||":return"??"===t.operator||"&&"===t.operator;case"&&":return"??"===t.operator;case"??":return"??"!==t.operator}},NullableTypeAnnotation:function(e,t){return Ry(t)},ObjectExpression:function(e,t,r){return Wy(r,3)},OptionalCallExpression:qy,OptionalIndexedAccessType:function(e,t){return Ty(t)&&t.objectType===e},OptionalMemberExpression:qy,SequenceExpression:function(e,t){var r=t.type;return!("ForStatement"===r||"ThrowStatement"===r||"ReturnStatement"===r||"IfStatement"===r&&t.test===e||"WhileStatement"===r&&t.test===e||"ForInStatement"===r&&t.right===e||"SwitchStatement"===r&&t.discriminant===e||"ExpressionStatement"===r&&t.expression===e)},TSAsExpression:By,TSInferType:function(e,t){var r=t.type;return"TSArrayType"===r||"TSOptionalType"===r},TSInstantiationExpression:function(e,t){var r=t.type;return("CallExpression"===r||"OptionalCallExpression"===r||"NewExpression"===r||"TSInstantiationExpression"===r)&&!!t.typeParameters},TSIntersectionType:My,TSSatisfiesExpression:By,TSTypeAssertion:By,TSUnionType:My,UnaryLike:Fy,UnionTypeAnnotation:Ny,UpdateExpression:function(e,t){return Oy(e,t)||Dy(e,t)},YieldExpression:Ly}),Vy=Ca,Hy=_,Ky=z,zy=X;function Xy(e){var t=new Map;function r(e,r){var a=t.get(e);t.set(e,a?function(e,t,n){var s;return null!=(s=a(e,t,n))?s:r(e,t,n)}:r)}for(var a=0,n=Object.keys(e);a=55296&&N<=56319&&D>I+1){var B=_.charCodeAt(I+1);if(B>=56320&&B<=57343){var M=(1024*(N-55296)+B-56320+65536).toString(16);b||(M=M.toUpperCase()),m+="\\u{"+M+"}",++I;continue}}}if(!l.escapeEverything){if(c.test(O)){m+=O;continue}if('"'==O){m+=h==O?'\\"':O;continue}if("`"==O){m+=h==O?"\\`":O;continue}if("'"==O){m+=h==O?"\\'":O;continue}}if("\0"!=O||y||d.test(_.charAt(I+1)))if(o.test(O))m+=i[O];else{var L=O.charCodeAt(0);if(l.minimal&&8232!=L&&8233!=L)m+=O;else{var F=L.toString(16);b||(F=F.toUpperCase());var U=F.length>2||y,q="\\"+(U?"u":"x")+("0000"+F).slice(U?-4:-2);m+=q}}else m+="\\0"}return l.wrap&&(m=h+m+h),"`"==h&&(m=m.replace(/\$\{/g,"\\${")),l.isScriptContext?m.replace(/<\/(script|style)/gi,"<\\/$1").replace(/|$)/g,"")}function g(e){return e.replace(/\/\*[\s\S]+?\*\//g,"")}function b(e){try{return f(e)}catch{return e}}function v(e){return e.replace(/\s/g,"")}function w(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function _(e,t){const n=f(t),r=`^\\s*?${w(e)}\\(\\s*?\\)`,o=new RegExp(r,"gm").exec(n)??[];return Boolean(o.length)}function x(...e){const t=e.map((e=>new RegExp(e).source)).join("");return new RegExp(t)}function E(e,t,n){const r=n?.capture??!1,o=n?.includeBody??!0,i=t?t.join("\\s*,\\s*"):"[^)]*",a="[^}]*",s=`function\\s*${e?"\\s"+w(e):""}\\s*\\(\\s*${i}\\s*\\)\\s*\\{`,c=`${a}\\}`,u=o?`${s}${c}`:`${s}`,l=`${e?`(let|const|var)?\\s?${w(e)}\\s*=\\s*`:""}\\(?\\s*${i}\\s*\\)?\\s*=>\\s*\\{?`,f=o?`${l}${a}\\}?`:`${l}`,p=`${e?`(let|const|var)?\\s?${w(e)}\\s*=\\s*function\\s*`:""}\\(\\s*${i}\\s*\\)\\s*\\{`;return new RegExp(`(${r?"":"?:"}${u}|${f}|${o?`${p}${c}`:`${p}`})`)}const O=/\(\?<([\w\d]+)>/g,S=/\\k<([\w\d]+)>/g;function A(e,{capture:t=!1,elementsSeparator:n=String.raw`\s*\|\|\s*`,permutationsSeparator:r="|"}={}){const o=function(e){const t=[];return function e(n,r){if(1!==r)for(let t=0;tnew RegExp(e).source))).map(((e,t)=>e.join(n).replace(O,String.raw`(?<$1_${t}>`).replace(S,String.raw`\k<$1_${t}>`))).join(r);return new RegExp(`(${t?"":"?:"}${o})`)}async function T(e,t){globalThis.IS_REACT_ACT_ENVIRONMENT=!0;const n=document.createElement("div"),r=globalThis.React?.createElement(e,t);return await(globalThis.React?.act((async()=>{globalThis.ReactDOM?.createRoot(n).render(r)}))),n}const k={astHelpers:'import ast\n\n# A chainable class that allows us to call functions on the result of parsing a string\n\n\nclass Node:\n def __init__(self, tree=None):\n if isinstance(tree, str):\n self.tree = ast.parse(tree)\n elif isinstance(tree, ast.AST) or tree == None:\n self.tree = tree\n else:\n raise TypeError("Node must be initialized with a string or AST")\n\n def __getitem__(self, i):\n if getattr(self.tree, "__getitem__", False):\n return Node(self.tree[i])\n elif getattr(self.tree, "body", False):\n return Node(self.tree.body[i])\n else:\n raise IndexError("Empty Nodes cannot be indexed.")\n\n def __len__(self):\n if getattr(self.tree, "__len__", False):\n return len(self.tree)\n if self.tree is None:\n return 0\n if not hasattr(self.tree, "body"):\n return 1\n return len(self.tree.body)\n\n def __eq__(self, other):\n if not isinstance(other, Node):\n return False\n if self.tree == None:\n return other.tree == None\n if other.tree == None:\n return False\n return ast.dump(self.tree, include_attributes=True) == ast.dump(\n other.tree, include_attributes=True\n )\n\n def __repr__(self):\n if self.tree == None:\n return "Node:\\nNone"\n return "Node:\\n" + ast.dump(self.tree, indent=2)\n\n def __str__(self):\n if self.tree == None:\n return "# no ast"\n return ast.unparse(self.tree)\n\n def _has_body(self):\n return bool(getattr(self.tree, "body", False))\n\n # "find" functions return a new node with the result of the find\n # function. In this case, it returns a new node with the function\n # definition (if it exists)\n\n def find_function(self, func):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.FunctionDef):\n if node.name == func:\n return Node(node)\n return Node()\n\n def find_functions(self, func):\n return [\n node\n for node in self._find_all((ast.FunctionDef, ast.AsyncFunctionDef))\n if node.tree.name == func\n ]\n\n def find_async_function(self, func):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.AsyncFunctionDef):\n if node.name == func:\n return Node(node)\n return Node()\n\n def find_awaits(self):\n return [\n node\n for node in self._find_all(ast.Expr)\n if isinstance(node.tree.value, ast.Await)\n ]\n\n def has_args(self, arg_str):\n if not isinstance(self.tree, (ast.FunctionDef, ast.AsyncFunctionDef)):\n return False\n dec_list = (f"@{Node(node)}" for node in self.tree.decorator_list)\n dec_str = "\\n".join(dec_list) + "\\n" if dec_list else ""\n if id := getattr(self.tree.returns, "id", False):\n returns = f" -> {id}"\n elif hasattr(self.tree.returns, "value"):\n returns = f" -> {self.tree.returns.value}"\n else:\n returns = ""\n async_kw = ""\n if isinstance(self.tree, ast.AsyncFunctionDef):\n async_kw = "async "\n body_lines = str(self.find_body()).split("\\n")\n new_body = "".join([f"\\n {line}" for line in body_lines])\n func_str = (\n f"{dec_str}{async_kw}def {self.tree.name}({arg_str}){returns}:{new_body}"\n )\n return self.is_equivalent(func_str)\n\n # returns_str is the annotation of the type returned by the function\n def has_returns(self, returns_str):\n if not isinstance(self.tree, (ast.FunctionDef, ast.AsyncFunctionDef)):\n return False\n return Node(self.tree.returns).is_equivalent(returns_str)\n\n def find_body(self):\n if not isinstance(self.tree, ast.AST):\n return Node()\n if not hasattr(self.tree, "body"):\n return Node()\n return Node(ast.Module(self.tree.body, []))\n\n # find the return statement of a function\n def find_return(self):\n if return_list := self._find_all(ast.Return):\n return return_list[0]\n return Node()\n\n def has_return(self, return_value):\n return self.find_return().is_equivalent(f"return {return_value}")\n\n def find_imports(self):\n return self._find_all((ast.Import, ast.ImportFrom))\n\n def find_comps(self):\n return [\n node\n for node in self._find_all(ast.Expr)\n if isinstance(\n node.tree.value,\n (ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp),\n )\n ]\n\n def _find_comp(\n self, classes=(ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp)\n ):\n if isinstance(self.tree, classes):\n return Node(self.tree)\n elif isinstance(self.tree, (ast.Assign, ast.AnnAssign, ast.Return)):\n if isinstance(self.tree.value, classes):\n return Node(self.tree.value)\n return Node()\n\n # find a list of iterables of a comprehension/generator expression\n def find_comp_iters(self):\n if not (node := self._find_comp()):\n return []\n return [Node(gen.iter) for gen in node.tree.generators]\n\n # find a list of targets (iteration variables) of a comprehension/generator expression\n def find_comp_targets(self):\n if not (node := self._find_comp()):\n return []\n return [Node(gen.target) for gen in node.tree.generators]\n\n # find the key of a dictionary comprehension\n def find_comp_key(self):\n if not (node := self._find_comp(ast.DictComp)):\n return Node()\n return Node(node.tree.key)\n\n # find the expression evaluated for a comprehension/generator expression\n # which is the value of the key in case of a dictionary comprehension\n def find_comp_expr(self):\n if not (node := self._find_comp()):\n return Node()\n if isinstance(node.tree, (ast.ListComp, ast.SetComp, ast.GeneratorExp)):\n return Node(node.tree.elt)\n elif isinstance(node.tree, ast.DictComp):\n return Node(node.tree.value)\n\n # find a list of `IfExpr`s at the end of the comprehension/generator expression\n def find_comp_ifs(self):\n if not (node := self._find_comp()):\n return []\n return [\n Node(gen.ifs[i])\n for gen in node.tree.generators\n for i in range(len(gen.ifs))\n ]\n\n # "has" functions return a boolean indicating whether whatever is being\n # searched for exists. In this case, it returns True if the variable exists.\n\n def has_variable(self, name):\n return self.find_variable(name) != Node()\n\n def has_import(self, import_str):\n return any(\n import_node.is_equivalent(import_str) for import_node in self.find_imports()\n )\n\n # find a list of function calls of the \'name\' function\n def find_calls(self, name):\n call_list = []\n for node in self._find_all(ast.Expr):\n if func := getattr(node.tree.value, "func", False):\n if isinstance(func, ast.Name) and func.id == name:\n call_list.append(Node(node.tree.value))\n elif isinstance(func, ast.Attribute) and func.attr == name:\n call_list.append(Node(node.tree.value))\n return call_list\n\n def has_call(self, call):\n return any(node.is_equivalent(call) for node in self._find_all(ast.Expr))\n\n def block_has_call(self, name, function=None):\n body = self if function is None else self.find_function(function).find_body()\n if body:\n for node in ast.walk(body.tree):\n fields = node._fields\n for n, field in enumerate(fields):\n if field == "attr" or field == "id" and fields[n + 1] == "ctx":\n if getattr(node, field, None) == name and isinstance(\n getattr(node, fields[n + 1], None), ast.Load\n ):\n return True\n return False\n\n def find_call_args(self):\n if not isinstance(self.tree, ast.Call):\n return []\n return [Node(arg) for arg in self.tree.args]\n\n def has_stmt(self, node_str):\n if not self._has_body():\n return False\n return any(Node(node).is_equivalent(node_str) for node in self.tree.body)\n\n def find_variable(self, name):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.Assign):\n for target in node.targets:\n if isinstance(target, ast.Name):\n if target.id == name:\n return Node(node)\n if isinstance(target, ast.Attribute):\n names = name.split(".")\n if target.value.id == names[0] and target.attr == names[1]:\n return Node(node)\n elif isinstance(node, ast.AnnAssign):\n if isinstance(node.target, ast.Name):\n if node.target.id == name:\n return Node(node)\n return Node()\n\n def find_variables(self, name):\n assignments = self._find_all((ast.Assign, ast.AnnAssign))\n var_list = []\n for node in assignments:\n if isinstance(node.tree, ast.Assign):\n for target in node.tree.targets:\n if isinstance(target, ast.Name):\n if target.id == name:\n var_list.append(node)\n if isinstance(target, ast.Attribute):\n names = name.split(".")\n if target.value.id == names[0] and target.attr == names[1]:\n var_list.append(node)\n elif isinstance(node.tree, ast.AnnAssign):\n if isinstance(node.tree.target, ast.Name):\n if node.tree.target.id == name:\n var_list.append(node)\n return var_list\n\n # find variable incremented or decremented using += or -=\n def find_aug_variable(self, name):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.AugAssign):\n if isinstance(node.target, ast.Name):\n if node.target.id == name:\n return Node(node)\n return Node()\n\n def get_variable(self, name):\n var = self.find_variable(name)\n if var != Node():\n return var.tree.value.value\n else:\n return None\n\n def has_function(self, name):\n return self.find_function(name) != Node()\n\n def has_class(self, name):\n return self.find_class(name) != Node()\n\n def has_decorators(self, *args):\n # the order of args does matter\n if not isinstance(self.tree, (ast.FunctionDef, ast.AsyncFunctionDef)):\n return False\n dec_list = (Node(node) for node in self.tree.decorator_list)\n return all(any(dec.is_equivalent(arg) for dec in dec_list) for arg in args)\n\n # Checks if the current scope contains a "pass" statement\n\n def has_pass(self):\n if isinstance(self.tree, (ast.If, ast.While, ast.For)):\n return False\n if getattr(self.tree, "body", False):\n return any(isinstance(node, ast.Pass) for node in self.tree.body)\n return False\n\n # Checks the variable, name, is in the current scope and is an integer\n\n def is_integer(self):\n if not isinstance(self.tree, ast.Assign):\n return False\n return type(self.tree.value.value) == type(1)\n\n def value_is_call(self, name):\n if not isinstance(self.tree, ast.Assign):\n return False\n call = self.tree.value\n if isinstance(call, ast.Call):\n return call.func.id == name\n return False\n\n # Loosely compares the code in target_str with the code represented by the\n # Node\'s AST. If the two codes are semantically equivalent (i.e. the same if\n # you ignore formatting and context) then this returns True, otherwise\n # False.\n #\n # Ignoring context means that the following comparison is True despite the\n # fact that the AST of `cond_node` is `Constant(value=True)` and `True`\n # compiles to `Module(body=[Expr(value=Constant(value=True))],\n # type_ignores=[])`:\n #\n # node = Node("if True:\\n pass") cond_node =\n # node.find_ifs()[0].find_conditions()[0] cond_node.is_equivalent("True")\n\n def is_equivalent(self, target_str):\n # Setting the tree to None is used to represent missing elements. Such\n # as the condition of a final else clause. It is, therefore, not\n # equivalent to any string.\n if self.tree == None:\n return False\n code_str = ast.unparse(self.tree)\n\n # Why parse and unparse again? Because of an edge case when comparing\n # the `target_str` "\'True\'" with the test in "if \'True\':". These should\n # be equivalent, but the condition unparses to "\'True\'", while the\n # `target_str` becomes \'"""True"""\' when parsed and unparsed again.\n\n # By parsing and unparsing `code_str` we get \'"""True"""\' and the\n # comparison returns True as expected.\n return ast.unparse(ast.parse(code_str)) == ast.unparse(ast.parse(target_str))\n\n def is_empty(self):\n return self.tree == None\n\n # Finds the class definition with the given name\n\n def find_class(self, class_name):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.ClassDef):\n if node.name == class_name:\n return Node(node)\n return Node()\n\n def inherits_from(self, *args):\n if not isinstance(self.tree, ast.ClassDef):\n return False\n if not self.tree.bases:\n return False\n id_list = [\n node.id if isinstance(node, ast.Name) else f"{node.value.id}.{node.attr}"\n for node in self.tree.bases\n ]\n return all(arg in id_list for arg in args)\n\n # Find an array of if statements\n\n def find_ifs(self):\n return self._find_all(ast.If)\n\n def _find_all(self, ast_type):\n return [Node(node) for node in self.tree.body if isinstance(node, ast_type)]\n\n def find_whiles(self):\n return self._find_all(ast.While)\n\n def find_for_loops(self):\n return self._find_all(ast.For)\n\n def find_for_vars(self):\n if not isinstance(self.tree, ast.For):\n return Node()\n return Node(self.tree.target)\n\n def find_for_iter(self):\n if not isinstance(self.tree, ast.For):\n return Node()\n return Node(self.tree.iter)\n\n def find_if(self, if_str):\n if_list = self._find_all(ast.If)\n for if_statement in if_list:\n if if_statement.find_conditions()[0].is_equivalent(if_str):\n return if_statement\n return Node()\n\n def find_while(self, while_str):\n while_list = self._find_all(ast.While)\n for while_loop in while_list:\n if while_loop.find_conditions()[0].is_equivalent(while_str):\n return while_loop\n return Node()\n\n def find_for(self, target_str, iter_str):\n for_list = self._find_all(ast.For)\n for for_loop in for_list:\n if for_loop.find_for_vars().is_equivalent(\n target_str\n ) and for_loop.find_for_iter().is_equivalent(iter_str):\n return for_loop\n return Node()\n\n # Find an array of bodies in if/elif statement and while or for loops\n\n def find_bodies(self):\n def _find_bodies(tree):\n if not isinstance(tree, (ast.If, ast.While, ast.For)):\n return []\n if tree.orelse == []:\n return [tree.body]\n if isinstance(tree.orelse[0], (ast.If, ast.While, ast.For)):\n return [tree.body] + _find_bodies(tree.orelse[0])\n\n return [tree.body] + [tree.orelse]\n\n return [Node(ast.Module(body, [])) for body in _find_bodies(self.tree)]\n\n # Find an array of conditions in if/elif statement or while loop\n\n def find_conditions(self):\n def _find_conditions(tree):\n if not isinstance(tree, (ast.If, ast.While)):\n return []\n test = tree.test\n if tree.orelse == []:\n return [test]\n if isinstance(tree.orelse[0], (ast.If, ast.While)):\n return [test] + _find_conditions(tree.orelse[0])\n\n return [test, None]\n\n return [Node(test) for test in _find_conditions(self.tree)]\n\n def find_matches(self):\n return self._find_all(ast.Match)\n\n def find_match_subject(self):\n if not isinstance(self.tree, ast.Match):\n return Node()\n return Node(self.tree.subject)\n\n def find_match_cases(self):\n if not isinstance(self.tree, ast.Match):\n return []\n return [Node(case) for case in self.tree.cases]\n\n def find_case_pattern(self):\n if not isinstance(self.tree, ast.match_case):\n return Node()\n return Node(self.tree.pattern)\n\n def find_case_guard(self):\n if not isinstance(self.tree, ast.match_case):\n return Node()\n if guard := getattr(self.tree, "guard", False):\n return Node(guard)\n return Node()\n\n def find_trys(self):\n return self._find_all(ast.Try)\n\n def find_excepts(self):\n if not isinstance(self.tree, ast.Try):\n return []\n return [Node(handler) for handler in self.tree.handlers]\n\n def find_except(self, except_type=None, name=None):\n if not isinstance(self.tree, ast.Try):\n return Node()\n for handler in self.tree.handlers:\n if except_type is None and handler.type is None:\n return Node(handler)\n if isinstance(handler.type, ast.Name):\n if handler.type.id == except_type:\n if (name is None and handler.name is None) or handler.name == name:\n return Node(handler)\n return Node()\n\n def has_except(self, except_type=None, name=None):\n if self.find_except(except_type, name) == Node():\n return False\n return True\n\n def find_try_else(self):\n if not isinstance(self.tree, ast.Try):\n return Node()\n if not self.tree.orelse:\n return Node()\n return Node(ast.Module(self.tree.orelse, []))\n\n def find_finally(self):\n if not isinstance(self.tree, ast.Try):\n return Node()\n if not self.tree.finalbody:\n return Node()\n return Node(ast.Module(self.tree.finalbody, []))\n\n # Returs a Boolean indicating if the statements passed as arguments\n # are found in the same order in the tree (statements can be non-consecutive)\n def is_ordered(self, *args):\n if not self._has_body():\n return False\n if len(args) < 2:\n return False\n arg_dict = {key: None for key in range(len(args))}\n for i, node in enumerate(self.tree.body):\n for j, arg in enumerate(args):\n if Node(node).is_equivalent(arg):\n arg_dict[j] = i\n break\n if None in arg_dict.values():\n return False\n return all(arg_dict[n] < arg_dict[n + 1] for n in range(len(arg_dict) - 1))\n',getDef(e,t){const n=new RegExp(`\\n?(? *?)def +${t} *\\((?[^\\)]*)\\)\\s*:\\s*?\\n?(?.*?)(?=\\n\\k[\\w#]|$)`,"s").exec(e);if(n){const{function_parameters:e,function_body:t,function_indentation:r}=n.groups,o=r.replace(/\n+/,"");return{def:n[0].replace(/^\n/,""),function_parameters:e,function_body:t,function_indentation:o.length}}return null},removeComments:e=>e.replace(/\/\/.*|\/\*[\s\S]*?\*\/|(#.*$)/gm,""),getBlock(e,t){const n=t instanceof RegExp?t.source:t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=new RegExp(`\\n?(? *?)(?${n})\\s*:\\s*?\\n(?(\\k +[^\\n]*| *\\n)+)(\n|$)`,"sm").exec(e);if(r){const{block_body:e,block_indentation:t,block_condition:n}=r.groups;return{block_body:e,block_condition:n,block_indentation:t.replace(/\n+/g,"").length}}return null}};class j{doc;constructor(e){this.doc=e}_getStyleRules(){const e=this.getStyleSheet();return this.styleSheetToCssRulesArray(e).filter((e=>e.type===CSSRule.STYLE_RULE))}getStyleDeclarations(e){return this._getStyleRules()?.filter((t=>t?.selectorText===e)).map((e=>e.style))}getStyle(e){const t=this._getStyleRules().find((t=>t?.selectorText===e))?.style;return t?(t.getPropVal=(e,n=!1)=>n?t.getPropertyValue(e).replace(/\s+/g,""):t.getPropertyValue(e),t):null}getStyleAny(e){for(const t of e){const e=this.getStyle(t);if(e)return e}return null}getStyleRule(e){const t=this._getStyleRules()?.find((t=>t?.selectorText===e));return t?{...t,isDeclaredAfter:e=>(e=>t=>{const n=Array.from(e.parentStyleSheet?.cssRules||[])?.filter((e=>e.type===CSSRule.STYLE_RULE)),r=n.find((e=>e?.selectorText===t));return!!r&&Array.from(e.parentStyleSheet?.cssRules||[]).indexOf(e)>Array.from(r?.parentStyleSheet?.cssRules||[]).indexOf(r)})(t)(e)}:null}getCSSRules(e){const t=this.getStyleSheet(),n=this.styleSheetToCssRulesArray(t);switch(e){case"media":return n.filter((e=>e.type===CSSRule.MEDIA_RULE));case"fontface":return n.filter((e=>e.type===CSSRule.FONT_FACE_RULE));case"import":return n.filter((e=>e.type===CSSRule.IMPORT_RULE));case"keyframes":return n.filter((e=>e.type===CSSRule.KEYFRAMES_RULE));default:return n}}isPropertyUsed(e){return this._getStyleRules().some((t=>t.style?.getPropertyValue(e)))}getRuleListsWithinMedia(e){const t=this.getCSSRules("media"),n=t?.find((t=>t?.media?.mediaText===e)),r=n?.cssRules;return Array.from(r||[])}getStyleSheet(){const e=this.doc?.querySelector("link[href*='styles']"),t=this.doc?.querySelector("style.fcc-injected-styles"),n=this.doc?.querySelector("style:not([class]):not([media])");return e?.sheet?.cssRules?.length?e.sheet:t?t.sheet:n?n.sheet:null}styleSheetToCssRulesArray(e){return Array.from(e?.cssRules||[])}selectorsFromSelector(e){const t=this.doc.querySelectorAll(e),n=Array.from(t).map((e=>{const t=[],n=[],r=[];for(;e.parentNode;){let o=e.tagName.toLowerCase();Array.from(e.parentNode.children).filter((t=>t.tagName===e.tagName)).length>1&&(o+=`:nth-child(${Array.from(e.parentNode.childNodes).indexOf(e)+1})`),t.unshift(o),n.unshift(o),r.push([t.join(" > "),n.join(" ")]),e=e.parentNode}return r.flat()})).flat();return[...new Set(n)]}}function N(e){const t=e.match(/(?:function\s*[^(]*\(([^)]*)\))/)||e.match(/(?:\b(?:const|let|var)\s*\w+\s*=\s*(?:function)?\s*\(([^)]*)\))/)||e.match(/=\s+([^)]*)=>/);return t?(t[1]||t[2]||t[3]||t[4]).replace(/[{}[\]]/g,"").split(",").map((e=>{const t=e.trim().split(/[=]/),n=t.length>1?t[1].replace(/['"]/g,"").trim():void 0;return{name:t[0].trim(),defaultValue:n}})):[]}function P(e,t,n=20){return n<1?Promise.reject(new Error(t)):e()?Promise.resolve():new Promise(((r,o)=>{setTimeout((()=>{P(e,t,n-1).then(r).catch(o)}),1)}))}},93531(e,t,n){"use strict";var r=n(13583),o=n(81948),i=n(75674);e.exports=function(){return i(r,o,arguments)}},93977(e,t,n){"use strict";var r=n(91242).every;function o(e,t,n,r){var o=!0;return n!==r.length-1&&(o=t.calledBefore(r[n+1])),!(!function(e,t){return void 0===e[t.id]&&(e[t.id]=0),e[t.id]1?arguments:e,o.bind(null,{}))}},94930(e,t,n){"use strict";var r=Function.call,o=n(32365),i=["size","caller","callee","arguments"];o&&i.push("__proto__"),e.exports=function(e){return Object.getOwnPropertyNames(e).reduce((function(t,n){return i.includes(n)||"function"!=typeof e[n]||(t[n]=r.bind(e[n])),t}),Object.create(null))}},95809(e){"use strict";e.exports=["Float16Array","Float32Array","Float64Array","Int8Array","Int16Array","Int32Array","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array"]},95938(e){var t=Object.getOwnPropertyDescriptor((function(){}),"length");e.exports=function(e,n,r){return t.configurable?(Object.defineProperty(e,"length",{get:function(){if(r)throw Error("Invalid Chai property: "+n+'.length. Due to a compatibility issue, "length" cannot directly follow "'+n+'". Use "'+n+'.lengthOf" instead.');throw Error("Invalid Chai property: "+n+'.length. See docs for proper usage of "'+n+'".')}}),e):e}},99183(e){"use strict";e.exports=Error},99427(e){e.exports=function(e,t){var n=e.Assertion,r=e.AssertionError,o=t.flag;function i(e,n){n&&o(this,"message",n),e=e.toLowerCase();var r=o(this,"object"),i=~["a","e","i","o","u"].indexOf(e.charAt(0))?"an ":"a ";this.assert(e===t.type(r).toLowerCase(),"expected #{this} to be "+i+e,"expected #{this} not to be "+i+e)}function a(e,n){return t.isNaN(e)&&t.isNaN(n)||e===n}function s(){o(this,"contains",!0)}function c(e,i){i&&o(this,"message",i);var s=o(this,"object"),c=t.type(s).toLowerCase(),u=o(this,"message"),l=o(this,"negate"),f=o(this,"ssfi"),p=o(this,"deep"),d=p?"deep ":"",h=p?o(this,"eql"):a;u=u?u+": ":"";var y=!1;switch(c){case"string":y=-1!==s.indexOf(e);break;case"weakset":if(p)throw new r(u+"unable to use .deep.include with WeakSet",void 0,f);y=s.has(e);break;case"map":s.forEach((function(t){y=y||h(t,e)}));break;case"set":p?s.forEach((function(t){y=y||h(t,e)})):y=s.has(e);break;case"array":y=p?s.some((function(t){return h(t,e)})):-1!==s.indexOf(e);break;default:if(e!==Object(e))throw new r(u+"the given combination of arguments ("+c+" and "+t.type(e).toLowerCase()+") is invalid for this assertion. You can use an array, a map, an object, a set, a string, or a weakset instead of a "+t.type(e).toLowerCase(),void 0,f);var m=Object.keys(e),g=null,b=0;if(m.forEach((function(i){var a=new n(s);if(t.transferFlags(this,a,!0),o(a,"lockSsfi",!0),l&&1!==m.length)try{a.property(i,e[i])}catch(e){if(!t.checkError.compatibleConstructor(e,r))throw e;null===g&&(g=e),b++}else a.property(i,e[i])}),this),l&&m.length>1&&b===m.length)throw g;return}this.assert(y,"expected #{this} to "+d+"include "+t.inspect(e),"expected #{this} to not "+d+"include "+t.inspect(e))}function u(){var e=o(this,"object");this.assert(null!=e,"expected #{this} to exist","expected #{this} to not exist")}function l(){var e=o(this,"object"),n=t.type(e);this.assert("Arguments"===n,"expected #{this} to be arguments but got "+n,"expected #{this} to not be arguments")}function f(e,t){t&&o(this,"message",t);var n=o(this,"object");if(o(this,"deep")){var r=o(this,"lockSsfi");o(this,"lockSsfi",!0),this.eql(e),o(this,"lockSsfi",r)}else this.assert(e===n,"expected #{this} to equal #{exp}","expected #{this} to not equal #{exp}",e,this._obj,!0)}function p(e,t){t&&o(this,"message",t);var n=o(this,"eql");this.assert(n(e,o(this,"object")),"expected #{this} to deeply equal #{exp}","expected #{this} to not deeply equal #{exp}",e,this._obj,!0)}function d(e,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"doLength"),u=o(this,"message"),l=u?u+": ":"",f=o(this,"ssfi"),p=t.type(s).toLowerCase(),d=t.type(e).toLowerCase(),h=!0;if(c&&"map"!==p&&"set"!==p&&new n(s,u,f,!0).to.have.property("length"),c||"date"!==p||"date"===d?"number"===d||!c&&"number"!==p?c||"date"===p||"number"===p?h=!1:a=l+"expected "+("string"===p?"'"+s+"'":s)+" to be a number or a date":a=l+"the argument to above must be a number":a=l+"the argument to above must be a date",h)throw new r(a,void 0,f);if(c){var y,m="length";"map"===p||"set"===p?(m="size",y=s.size):y=s.length,this.assert(y>e,"expected #{this} to have a "+m+" above #{exp} but got #{act}","expected #{this} to not have a "+m+" above #{exp}",e,y)}else this.assert(s>e,"expected #{this} to be above #{exp}","expected #{this} to be at most #{exp}",e)}function h(e,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"doLength"),u=o(this,"message"),l=u?u+": ":"",f=o(this,"ssfi"),p=t.type(s).toLowerCase(),d=t.type(e).toLowerCase(),h=!0;if(c&&"map"!==p&&"set"!==p&&new n(s,u,f,!0).to.have.property("length"),c||"date"!==p||"date"===d?"number"===d||!c&&"number"!==p?c||"date"===p||"number"===p?h=!1:a=l+"expected "+("string"===p?"'"+s+"'":s)+" to be a number or a date":a=l+"the argument to least must be a number":a=l+"the argument to least must be a date",h)throw new r(a,void 0,f);if(c){var y,m="length";"map"===p||"set"===p?(m="size",y=s.size):y=s.length,this.assert(y>=e,"expected #{this} to have a "+m+" at least #{exp} but got #{act}","expected #{this} to have a "+m+" below #{exp}",e,y)}else this.assert(s>=e,"expected #{this} to be at least #{exp}","expected #{this} to be below #{exp}",e)}function y(e,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"doLength"),u=o(this,"message"),l=u?u+": ":"",f=o(this,"ssfi"),p=t.type(s).toLowerCase(),d=t.type(e).toLowerCase(),h=!0;if(c&&"map"!==p&&"set"!==p&&new n(s,u,f,!0).to.have.property("length"),c||"date"!==p||"date"===d?"number"===d||!c&&"number"!==p?c||"date"===p||"number"===p?h=!1:a=l+"expected "+("string"===p?"'"+s+"'":s)+" to be a number or a date":a=l+"the argument to below must be a number":a=l+"the argument to below must be a date",h)throw new r(a,void 0,f);if(c){var y,m="length";"map"===p||"set"===p?(m="size",y=s.size):y=s.length,this.assert(ye===t,b="";d&&(b+="deep "),s&&(b+="own "),a&&(b+="nested "),b+="property ",p=s?Object.prototype.hasOwnProperty.call(u,e):a?y.exists:t.hasProperty(u,e),h&&1!==arguments.length||this.assert(p,"expected #{this} to have "+b+t.inspect(e),"expected #{this} to not have "+b+t.inspect(e)),arguments.length>1&&this.assert(p&&g(n,m),"expected #{this} to have "+b+t.inspect(e)+" of #{exp}, but got #{act}","expected #{this} to not have "+b+t.inspect(e)+" of #{act}",n,m),o(this,"object",m)}function v(e,t,n){o(this,"own",!0),b.apply(this,arguments)}function w(e,n,r){"string"==typeof n&&(r=n,n=null),r&&o(this,"message",r);var i=o(this,"object"),a=Object.getOwnPropertyDescriptor(Object(i),e),s=o(this,"eql");a&&n?this.assert(s(n,a),"expected the own property descriptor for "+t.inspect(e)+" on #{this} to match "+t.inspect(n)+", got "+t.inspect(a),"expected the own property descriptor for "+t.inspect(e)+" on #{this} to not match "+t.inspect(n),n,a,!0):this.assert(a,"expected #{this} to have an own property descriptor for "+t.inspect(e),"expected #{this} to not have an own property descriptor for "+t.inspect(e)),o(this,"object",a)}function _(){o(this,"doLength",!0)}function x(e,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=t.type(a).toLowerCase(),c=o(this,"message"),u=o(this,"ssfi"),l="length";switch(s){case"map":case"set":l="size",i=a.size;break;default:new n(a,c,u,!0).to.have.property("length"),i=a.length}this.assert(i==e,"expected #{this} to have a "+l+" of #{exp} but got #{act}","expected #{this} to not have a "+l+" of #{act}",e,i)}function E(e,t){t&&o(this,"message",t);var n=o(this,"object");this.assert(e.exec(n),"expected #{this} to match "+e,"expected #{this} not to match "+e)}function O(e){var n,i,a=o(this,"object"),s=t.type(a),c=t.type(e),u=o(this,"ssfi"),l=o(this,"deep"),f="",p=!0,d=o(this,"message"),h=(d=d?d+": ":"")+"when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments";if("Map"===s||"Set"===s)f=l?"deeply ":"",i=[],a.forEach((function(e,t){i.push(t)})),"Array"!==c&&(e=Array.prototype.slice.call(arguments));else{switch(i=t.getOwnEnumerableProperties(a),c){case"Array":if(arguments.length>1)throw new r(h,void 0,u);break;case"Object":if(arguments.length>1)throw new r(h,void 0,u);e=Object.keys(e);break;default:e=Array.prototype.slice.call(arguments)}e=e.map((function(e){return"symbol"==typeof e?e:String(e)}))}if(!e.length)throw new r(d+"keys required",void 0,u);var y=e.length,m=o(this,"any"),g=o(this,"all"),b=e,v=l?o(this,"eql"):(e,t)=>e===t;if(m||g||(g=!0),m&&(p=b.some((function(e){return i.some((function(t){return v(e,t)}))}))),g&&(p=b.every((function(e){return i.some((function(t){return v(e,t)}))})),o(this,"contains")||(p=p&&e.length==i.length)),y>1){var w=(e=e.map((function(e){return t.inspect(e)}))).pop();g&&(n=e.join(", ")+", and "+w),m&&(n=e.join(", ")+", or "+w)}else n=t.inspect(e[0]);n=(y>1?"keys ":"key ")+n,n=(o(this,"contains")?"contain ":"have ")+n,this.assert(p,"expected #{this} to "+f+n,"expected #{this} to not "+f+n,b.slice(0).sort(t.compareByInspect),i.sort(t.compareByInspect),!0)}function S(e,r,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"ssfi"),u=o(this,"message"),l=o(this,"negate")||!1;new n(s,u,c,!0).is.a("function"),(e instanceof RegExp||"string"==typeof e)&&(r=e,e=null);try{s()}catch(e){a=e}var f=void 0===e&&void 0===r,p=Boolean(e&&r),d=!1,h=!1;if(f||!f&&!l){var y="an error";e instanceof Error?y="#{exp}":e&&(y=t.checkError.getConstructorName(e)),this.assert(a,"expected #{this} to throw "+y,"expected #{this} to not throw an error but #{act} was thrown",e&&e.toString(),a instanceof Error?a.toString():"string"==typeof a?a:a&&t.checkError.getConstructorName(a))}if(e&&a&&(e instanceof Error&&t.checkError.compatibleInstance(a,e)===l&&(p&&l?d=!0:this.assert(l,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(a&&!l?" but #{act} was thrown":""),e.toString(),a.toString())),t.checkError.compatibleConstructor(a,e)===l&&(p&&l?d=!0:this.assert(l,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(a?" but #{act} was thrown":""),e instanceof Error?e.toString():e&&t.checkError.getConstructorName(e),a instanceof Error?a.toString():a&&t.checkError.getConstructorName(a)))),a&&null!=r){var m="including";r instanceof RegExp&&(m="matching"),t.checkError.compatibleMessage(a,r)===l&&(p&&l?h=!0:this.assert(l,"expected #{this} to throw error "+m+" #{exp} but got #{act}","expected #{this} to throw error not "+m+" #{exp}",r,t.checkError.getMessage(a)))}d&&h&&this.assert(l,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(a?" but #{act} was thrown":""),e instanceof Error?e.toString():e&&t.checkError.getConstructorName(e),a instanceof Error?a.toString():a&&t.checkError.getConstructorName(a)),o(this,"object",a)}function A(e,n){n&&o(this,"message",n);var r=o(this,"object"),i=o(this,"itself"),a="function"!=typeof r||i?r[e]:r.prototype[e];this.assert("function"==typeof a,"expected #{this} to respond to "+t.inspect(e),"expected #{this} to not respond to "+t.inspect(e))}function T(e,n){n&&o(this,"message",n);var r=e(o(this,"object"));this.assert(r,"expected #{this} to satisfy "+t.objDisplay(e),"expected #{this} to not satisfy"+t.objDisplay(e),!o(this,"negate"),r)}function k(e,t,i){i&&o(this,"message",i);var a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");if(new n(a,s,c,!0).is.a("number"),"number"!=typeof e||"number"!=typeof t)throw new r((s=s?s+": ":"")+"the arguments to closeTo or approximately must be numbers"+(void 0===t?", and a delta is required":""),void 0,c);this.assert(Math.abs(a-e)<=t,"expected #{this} to be close to "+e+" +/- "+t,"expected #{this} not to be close to "+e+" +/- "+t)}function j(e,t,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");new n(a,s,c,!0).is.a("function"),t?(new n(e,s,c,!0).to.have.property(t),i=e[t]):(new n(e,s,c,!0).is.a("function"),i=e()),a();var u=null==t?e():e[t],l=null==t?i:"."+t;o(this,"deltaMsgObj",l),o(this,"initialDeltaValue",i),o(this,"finalDeltaValue",u),o(this,"deltaBehavior","change"),o(this,"realDelta",u!==i),this.assert(i!==u,"expected "+l+" to change","expected "+l+" to not change")}function N(e,t,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");new n(a,s,c,!0).is.a("function"),t?(new n(e,s,c,!0).to.have.property(t),i=e[t]):(new n(e,s,c,!0).is.a("function"),i=e()),new n(i,s,c,!0).is.a("number"),a();var u=null==t?e():e[t],l=null==t?i:"."+t;o(this,"deltaMsgObj",l),o(this,"initialDeltaValue",i),o(this,"finalDeltaValue",u),o(this,"deltaBehavior","increase"),o(this,"realDelta",u-i),this.assert(u-i>0,"expected "+l+" to increase","expected "+l+" to not increase")}function P(e,t,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");new n(a,s,c,!0).is.a("function"),t?(new n(e,s,c,!0).to.have.property(t),i=e[t]):(new n(e,s,c,!0).is.a("function"),i=e()),new n(i,s,c,!0).is.a("number"),a();var u=null==t?e():e[t],l=null==t?i:"."+t;o(this,"deltaMsgObj",l),o(this,"initialDeltaValue",i),o(this,"finalDeltaValue",u),o(this,"deltaBehavior","decrease"),o(this,"realDelta",i-u),this.assert(u-i<0,"expected "+l+" to decrease","expected "+l+" to not decrease")}["to","be","been","is","and","has","have","with","that","which","at","of","same","but","does","still","also"].forEach((function(e){n.addProperty(e)})),n.addProperty("not",(function(){o(this,"negate",!0)})),n.addProperty("deep",(function(){o(this,"deep",!0)})),n.addProperty("nested",(function(){o(this,"nested",!0)})),n.addProperty("own",(function(){o(this,"own",!0)})),n.addProperty("ordered",(function(){o(this,"ordered",!0)})),n.addProperty("any",(function(){o(this,"any",!0),o(this,"all",!1)})),n.addProperty("all",(function(){o(this,"all",!0),o(this,"any",!1)})),n.addChainableMethod("an",i),n.addChainableMethod("a",i),n.addChainableMethod("include",c,s),n.addChainableMethod("contain",c,s),n.addChainableMethod("contains",c,s),n.addChainableMethod("includes",c,s),n.addProperty("ok",(function(){this.assert(o(this,"object"),"expected #{this} to be truthy","expected #{this} to be falsy")})),n.addProperty("true",(function(){this.assert(!0===o(this,"object"),"expected #{this} to be true","expected #{this} to be false",!o(this,"negate"))})),n.addProperty("false",(function(){this.assert(!1===o(this,"object"),"expected #{this} to be false","expected #{this} to be true",!!o(this,"negate"))})),n.addProperty("null",(function(){this.assert(null===o(this,"object"),"expected #{this} to be null","expected #{this} not to be null")})),n.addProperty("undefined",(function(){this.assert(void 0===o(this,"object"),"expected #{this} to be undefined","expected #{this} not to be undefined")})),n.addProperty("NaN",(function(){this.assert(t.isNaN(o(this,"object")),"expected #{this} to be NaN","expected #{this} not to be NaN")})),n.addProperty("exist",u),n.addProperty("exists",u),n.addProperty("empty",(function(){var e,n=o(this,"object"),i=o(this,"ssfi"),a=o(this,"message");switch(a=a?a+": ":"",t.type(n).toLowerCase()){case"array":case"string":e=n.length;break;case"map":case"set":e=n.size;break;case"weakmap":case"weakset":throw new r(a+".empty was passed a weak collection",void 0,i);case"function":var s=a+".empty was passed a function "+t.getName(n);throw new r(s.trim(),void 0,i);default:if(n!==Object(n))throw new r(a+".empty was passed non-string primitive "+t.inspect(n),void 0,i);e=Object.keys(n).length}this.assert(0===e,"expected #{this} to be empty","expected #{this} not to be empty")})),n.addProperty("arguments",l),n.addProperty("Arguments",l),n.addMethod("equal",f),n.addMethod("equals",f),n.addMethod("eq",f),n.addMethod("eql",p),n.addMethod("eqls",p),n.addMethod("above",d),n.addMethod("gt",d),n.addMethod("greaterThan",d),n.addMethod("least",h),n.addMethod("gte",h),n.addMethod("greaterThanOrEqual",h),n.addMethod("below",y),n.addMethod("lt",y),n.addMethod("lessThan",y),n.addMethod("most",m),n.addMethod("lte",m),n.addMethod("lessThanOrEqual",m),n.addMethod("within",(function(e,i,a){a&&o(this,"message",a);var s,c=o(this,"object"),u=o(this,"doLength"),l=o(this,"message"),f=l?l+": ":"",p=o(this,"ssfi"),d=t.type(c).toLowerCase(),h=t.type(e).toLowerCase(),y=t.type(i).toLowerCase(),m=!0,g="date"===h&&"date"===y?e.toISOString()+".."+i.toISOString():e+".."+i;if(u&&"map"!==d&&"set"!==d&&new n(c,l,p,!0).to.have.property("length"),u||"date"!==d||"date"===h&&"date"===y?"number"===h&&"number"===y||!u&&"number"!==d?u||"date"===d||"number"===d?m=!1:s=f+"expected "+("string"===d?"'"+c+"'":c)+" to be a number or a date":s=f+"the arguments to within must be numbers":s=f+"the arguments to within must be dates",m)throw new r(s,void 0,p);if(u){var b,v="length";"map"===d||"set"===d?(v="size",b=c.size):b=c.length,this.assert(b>=e&&b<=i,"expected #{this} to have a "+v+" within "+g,"expected #{this} to not have a "+v+" within "+g)}else this.assert(c>=e&&c<=i,"expected #{this} to be within "+g,"expected #{this} to not be within "+g)})),n.addMethod("instanceof",g),n.addMethod("instanceOf",g),n.addMethod("property",b),n.addMethod("ownProperty",v),n.addMethod("haveOwnProperty",v),n.addMethod("ownPropertyDescriptor",w),n.addMethod("haveOwnPropertyDescriptor",w),n.addChainableMethod("length",x,_),n.addChainableMethod("lengthOf",x,_),n.addMethod("match",E),n.addMethod("matches",E),n.addMethod("string",(function(e,r){r&&o(this,"message",r);var i=o(this,"object"),a=o(this,"message"),s=o(this,"ssfi");new n(i,a,s,!0).is.a("string"),this.assert(~i.indexOf(e),"expected #{this} to contain "+t.inspect(e),"expected #{this} to not contain "+t.inspect(e))})),n.addMethod("keys",O),n.addMethod("key",O),n.addMethod("throw",S),n.addMethod("throws",S),n.addMethod("Throw",S),n.addMethod("respondTo",A),n.addMethod("respondsTo",A),n.addProperty("itself",(function(){o(this,"itself",!0)})),n.addMethod("satisfy",T),n.addMethod("satisfies",T),n.addMethod("closeTo",k),n.addMethod("approximately",k),n.addMethod("members",(function(e,t){t&&o(this,"message",t);var r=o(this,"object"),i=o(this,"message"),a=o(this,"ssfi");new n(r,i,a,!0).to.be.an("array"),new n(e,i,a,!0).to.be.an("array");var s,c,u,l=o(this,"contains"),f=o(this,"ordered");l?(c="expected #{this} to be "+(s=f?"an ordered superset":"a superset")+" of #{exp}",u="expected #{this} to not be "+s+" of #{exp}"):(c="expected #{this} to have the same "+(s=f?"ordered members":"members")+" as #{exp}",u="expected #{this} to not have the same "+s+" as #{exp}");var p=o(this,"deep")?o(this,"eql"):void 0;this.assert(function(e,t,n,r,o){if(!r){if(e.length!==t.length)return!1;t=t.slice()}return e.every((function(e,i){if(o)return n?n(e,t[i]):e===t[i];if(!n){var a=t.indexOf(e);return-1!==a&&(r||t.splice(a,1),!0)}return t.some((function(o,i){return!!n(e,o)&&(r||t.splice(i,1),!0)}))}))}(e,r,p,l,f),c,u,e,r,!0)})),n.addMethod("oneOf",(function(e,t){t&&o(this,"message",t);var r=o(this,"object"),i=o(this,"message"),a=o(this,"ssfi"),s=o(this,"contains"),c=o(this,"deep"),u=o(this,"eql");new n(e,i,a,!0).to.be.an("array"),s?this.assert(e.some((function(e){return r.indexOf(e)>-1})),"expected #{this} to contain one of #{exp}","expected #{this} to not contain one of #{exp}",e,r):c?this.assert(e.some((function(e){return u(r,e)})),"expected #{this} to deeply equal one of #{exp}","expected #{this} to deeply equal one of #{exp}",e,r):this.assert(e.indexOf(r)>-1,"expected #{this} to be one of #{exp}","expected #{this} to not be one of #{exp}",e,r)})),n.addMethod("change",j),n.addMethod("changes",j),n.addMethod("increase",N),n.addMethod("increases",N),n.addMethod("decrease",P),n.addMethod("decreases",P),n.addMethod("by",(function(e,t){t&&o(this,"message",t);var n,r=o(this,"deltaMsgObj"),i=o(this,"initialDeltaValue"),a=o(this,"finalDeltaValue"),s=o(this,"deltaBehavior"),c=o(this,"realDelta");n="change"===s?Math.abs(a-i)===Math.abs(e):c===Math.abs(e),this.assert(n,"expected "+r+" to "+s+" by "+e,"expected "+r+" to not "+s+" by "+e)})),n.addProperty("extensible",(function(){var e=o(this,"object"),t=e===Object(e)&&Object.isExtensible(e);this.assert(t,"expected #{this} to be extensible","expected #{this} to not be extensible")})),n.addProperty("sealed",(function(){var e=o(this,"object"),t=e!==Object(e)||Object.isSealed(e);this.assert(t,"expected #{this} to be sealed","expected #{this} to not be sealed")})),n.addProperty("frozen",(function(){var e=o(this,"object"),t=e!==Object(e)||Object.isFrozen(e);this.assert(t,"expected #{this} to be frozen","expected #{this} to not be frozen")})),n.addProperty("finite",(function(e){var t=o(this,"object");this.assert("number"==typeof t&&isFinite(t),"expected #{this} to be a finite number","expected #{this} to not be a finite number")}))}}},__webpack_module_cache__={},leafPrototypes,getProto,inProgress,dataWebpackPrefix;function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={id:e,loaded:!1,exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.loaded=!0,n.exports}__webpack_require__.m=__webpack_modules__,__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},getProto=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,__webpack_require__.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var n=Object.create(null);__webpack_require__.r(n);var r={};leafPrototypes=leafPrototypes||[null,getProto({}),getProto([]),getProto(getProto)];for(var o=2&t&&e;("object"==typeof o||"function"==typeof o)&&!~leafPrototypes.indexOf(o);o=getProto(o))Object.getOwnPropertyNames(o).forEach((t=>r[t]=()=>e[t]));return r.default=()=>e,__webpack_require__.d(n,r),n},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.f={},__webpack_require__.e=e=>Promise.all(Object.keys(__webpack_require__.f).reduce(((t,n)=>(__webpack_require__.f[n](e,t),t)),[])),__webpack_require__.u=e=>(({111:"enzyme",645:"enzyme-adapter",726:"explorer"}[e]||e)+"."+{26:"d4b48aa36b1383e317ab",111:"a8f1009fa72e3f86807e",325:"713e5e15bda3843ab320",361:"dca39c129f300a617e4f",645:"a8f1009fa72e3f86807e",726:"59c927c94d2bdb7bd844"}[e]+".js"),__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),inProgress={},dataWebpackPrefix="@freecodecamp/curriculum-helpers:",__webpack_require__.l=(e,t,n,r)=>{if(inProgress[e])inProgress[e].push(t);else{var o,i;if(void 0!==n)for(var a=document.getElementsByTagName("script"),s=0;s{o.onerror=o.onload=null,clearTimeout(l);var r=inProgress[e];if(delete inProgress[e],o.parentNode&&o.parentNode.removeChild(o),r&&r.forEach((e=>e(n))),t)return t(n)},l=setTimeout(u.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=u.bind(null,o.onerror),o.onload=u.bind(null,o.onload),i&&document.head.appendChild(o)}},__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;__webpack_require__.g.importScripts&&(e=__webpack_require__.g.location+"");var t=__webpack_require__.g.document;if(!e&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){var n=t.getElementsByTagName("script");if(n.length)for(var r=n.length-1;r>-1&&(!e||!/^http(s?):/.test(e));)e=n[r--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),__webpack_require__.p=e})(),(()=>{var e={782:0};__webpack_require__.f.j=(t,n)=>{var r=__webpack_require__.o(e,t)?e[t]:void 0;if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(((n,o)=>r=e[t]=[n,o]));n.push(r[2]=o);var i=__webpack_require__.p+__webpack_require__.u(t),a=new Error;__webpack_require__.l(i,(n=>{if(__webpack_require__.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var o=n&&("load"===n.type?"missing":n.type),i=n&&n.target&&n.target.src;a.message="Loading chunk "+t+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,r[1](a)}}),"chunk-"+t,t)}};var t=(t,n)=>{var r,o,[i,a,s]=n,c=0;if(i.some((t=>0!==e[t]))){for(r in a)__webpack_require__.o(a,r)&&(__webpack_require__.m[r]=a[r]);s&&s(__webpack_require__)}for(t&&t(n);c{"use strict";var jquery__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(87569),jquery__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_0__),_helpers_lib__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(92920),_sinonjs_fake_timers__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(6844),chai__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(22222),_shared_src_messages__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(83443),_shared_src_ids__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(71489),_mock_local_storage__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(57879),_shared_src_proxy_console__WEBPACK_IMPORTED_MODULE_7__=__webpack_require__(76963),_shared_src_format__WEBPACK_IMPORTED_MODULE_8__=__webpack_require__(9060),_shared_src_async_iife__WEBPACK_IMPORTED_MODULE_9__=__webpack_require__(22707),_shared_src_proxy_fetch__WEBPACK_IMPORTED_MODULE_10__=__webpack_require__(79033);const READY_MESSAGE={type:"ready"};globalThis.$=jquery__WEBPACK_IMPORTED_MODULE_0___default(),globalThis.__FakeTimers=_sinonjs_fake_timers__WEBPACK_IMPORTED_MODULE_2__,globalThis.assert=chai__WEBPACK_IMPORTED_MODULE_3__.vA,globalThis.__helpers=_helpers_lib__WEBPACK_IMPORTED_MODULE_1__,Object.defineProperty(globalThis,"localStorage",{value:new _mock_local_storage__WEBPACK_IMPORTED_MODULE_6__.Z});const removeTestScripts=()=>{const e=document.getElementById(_shared_src_ids__WEBPACK_IMPORTED_MODULE_5__.V);e?.remove();const t=document.getElementById(_shared_src_ids__WEBPACK_IMPORTED_MODULE_5__.o);t?.remove()};document.addEventListener("submit",(e=>{e.preventDefault()})),globalThis.fetch=(0,_shared_src_proxy_fetch__WEBPACK_IMPORTED_MODULE_10__.V)(parent);class DOMTestEvaluator{#a;#n;#s(e){return{err:{message:e.message,stack:e.stack,...!!e.expected&&{expected:e.expected},...!!e.actual&&{actual:e.actual}}}}constructor(e=new _shared_src_proxy_console__WEBPACK_IMPORTED_MODULE_7__.Y(globalThis.console,_shared_src_format__WEBPACK_IMPORTED_MODULE_8__.G)){this.#n=e}async init(opts){removeTestScripts();const codeObj=opts.code,code=(codeObj?.contents??"").slice(),editableContents=(codeObj?.editableContents??"").slice(),__testEditable=e=>{const t=document.createElement("div");t.id="editable-only",t.innerHTML=editableContents,document.body.appendChild(t);const n=e();return document.body.removeChild(t),n},DeepFreeze=e=>(Object.freeze(e),Object.getOwnPropertyNames(e).forEach((t=>{!Object.hasOwn(e,t)||null===e[t]||"object"!=typeof e[t]&&"function"!=typeof e[t]||Object.isFrozen(e[t])||DeepFreeze(e[t])})),e);let Enzyme;if(opts.loadEnzyme){let e;[{default:Enzyme},{default:e}]=await Promise.all([Promise.all([__webpack_require__.e(361),__webpack_require__.e(111)]).then(__webpack_require__.t.bind(__webpack_require__,71361,23)),Promise.all([__webpack_require__.e(361),__webpack_require__.e(26),__webpack_require__.e(645)]).then(__webpack_require__.t.bind(__webpack_require__,21645,23))]),Enzyme.configure({adapter:new e})}return this.#a=async function(rawTest){this.#n.on();try{const testWithBefore=`${opts.hooks?.beforeEach??""};\n${rawTest}`,test=(0,_shared_src_async_iife__WEBPACK_IMPORTED_MODULE_9__.o)(testWithBefore);return await eval(test),{pass:!0,...this.#n.flush()}}catch(e){this.#n.off(),console.error(e);const t=e;return{...this.#s(t),...this.#n.flush()}}finally{this.#n.off();try{opts.hooks?.afterEach&&eval(opts.hooks.afterEach)}catch(e){return this.#s(e)}}},Promise.resolve()}async runTest(e){return this.#a(e)}async runCode(code){try{await eval(code)}catch(e){console.error("Error evaluating code:",code,e)}}async handleMessage(e){const t=t=>e.ports[0].postMessage(t);if("test"===e.data.type){const n={type:"result",value:await this.#a(e.data.value)};(0,_shared_src_messages__WEBPACK_IMPORTED_MODULE_4__.z)(t,n)}else"init"===e.data.type?(await this.init(e.data.value),t(READY_MESSAGE)):"code"===e.data.type&&(await this.runCode(e.data.value),t({type:"code"}))}}const evaluator=new DOMTestEvaluator;onmessage=function(e){e.source===self.parent&&evaluator.handleMessage(e)}})()})(); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt b/mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt new file mode 100644 index 000000000..cea3cf725 --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt @@ -0,0 +1,550 @@ +/*! + * ### ._obj + * + * Quick reference to stored `actual` value for plugin developers. + * + * @api private + */ + +/*! + * ### .ifError(object) + * + * Asserts if value is not a false value, and throws if it is a true value. + * This is added to allow for chai to be a drop-in replacement for Node's + * assert class. + * + * var err = new Error('I am a custom error'); + * assert.ifError(err); // Rethrows err! + * + * @name ifError + * @param {Object} object + * @namespace Assert + * @api public + */ + +/*! + * Aliases. + */ + +/*! + * Assertion Constructor + * + * Creates object for chaining. + * + * `Assertion` objects contain metadata in the form of flags. Three flags can + * be assigned during instantiation by passing arguments to this constructor: + * + * - `object`: This flag contains the target of the assertion. For example, in + * the assertion `expect(numKittens).to.equal(7);`, the `object` flag will + * contain `numKittens` so that the `equal` assertion can reference it when + * needed. + * + * - `message`: This flag contains an optional custom error message to be + * prepended to the error message that's generated by the assertion when it + * fails. + * + * - `ssfi`: This flag stands for "start stack function indicator". It + * contains a function reference that serves as the starting point for + * removing frames from the stack trace of the error that's created by the + * assertion when it fails. The goal is to provide a cleaner stack trace to + * end users by removing Chai's internal functions. Note that it only works + * in environments that support `Error.captureStackTrace`, and only when + * `Chai.config.includeStack` hasn't been set to `false`. + * + * - `lockSsfi`: This flag controls whether or not the given `ssfi` flag + * should retain its current value, even as assertions are chained off of + * this object. This is usually set to `true` when creating a new assertion + * from within another assertion. It's also temporarily set to `true` before + * an overwritten assertion gets called by the overwriting assertion. + * + * - `eql`: This flag contains the deepEqual function to be used by the assertion. + * + * @param {Mixed} obj target of the assertion + * @param {String} msg (optional) custom error message + * @param {Function} ssfi (optional) starting point for removing stack frames + * @param {Boolean} lockSsfi (optional) whether or not the ssfi flag is locked + * @api private + */ + +/*! + * Chai dependencies. + */ + +/*! + * Module dependencies. + */ + +/*! + * Module export. + */ + +/*! + * Add a chainable method + */ + +/*! + * Assert interface + */ + +/*! + * Assertion Error + */ + +/*! + * Chai - addChainingMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - addLengthGuard utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - addMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - addProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - compareByInspect utility + * Copyright(c) 2011-2016 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - expectTypes utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - flag utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - getActual utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - getOwnEnumerableProperties utility + * Copyright(c) 2011-2016 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - getOwnEnumerablePropertySymbols utility + * Copyright(c) 2011-2016 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - getProperties utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - isNaN utility + * Copyright(c) 2012-2015 Sakthipriyan Vairamani + * MIT Licensed + */ + +/*! + * Chai - isProxyEnabled helper + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - message composition utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - overwriteChainableMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - overwriteMethod utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - overwriteProperty utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - test utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai - transferFlags utility + * Copyright(c) 2012-2014 Jake Luer + * MIT Licensed + */ + +/*! + * Chai version + */ + +/*! + * Check if a property exists + */ + +/*! + * Check to see if the MemoizeMap has recorded a result of the two operands + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {MemoizeMap} memoizeMap + * @returns {Boolean|null} result +*/ + +/*! + * Checks error against a given set of criteria + */ + +/*! + * Compare by inspect method + */ + +/*! + * Compare two Regular Expressions for equality. + * + * @param {RegExp} leftHandOperand + * @param {RegExp} rightHandOperand + * @return {Boolean} result + */ + +/*! + * Compare two Sets/Maps for equality. Faster than other equality functions. + * + * @param {Set} leftHandOperand + * @param {Set} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +/*! + * Configuration + */ + +/*! + * Core Assertions + */ + +/*! + * Deep equal utility + */ + +/*! + * Deep path info + */ + +/*! + * Dependencies that are used for multiple exports are required here only once + */ + +/*! + * Determine if the given object has an @@iterator function. + * + * @param {Object} target + * @return {Boolean} `true` if the object has an @@iterator function. + */ + +/*! + * Determines if two objects have matching values, given a set of keys. Defers to deepEqual for the equality check of + * each key. If any value of the given key is not equal, the function will return false (early). + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {Array} keys An array of keys to compare the values of leftHandOperand and rightHandOperand against + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +/*! + * Ensure correct constructor + */ + +/*! + * Expect interface + */ + +/*! + * Flag transferring utility + */ + +/*! + * Flag utility + */ + +/*! + * Function name + */ + +/*! + * Get own enumerable properties method + */ + +/*! + * Get own enumerable property symbols method + */ + +/*! + * Gets all entries from a Generator. This will consume the generator - which could have side effects. + * + * @param {Generator} target + * @returns {Array} an array of entries from the Generator. + */ + +/*! + * Gets all iterator entries from the given Object. If the Object has no @@iterator function, returns an empty array. + * This will consume the iterator - which could have side effects depending on the @@iterator implementation. + * + * @param {Object} target + * @returns {Array} an array of entries from the @@iterator function + */ + +/*! + * Gets all own and inherited enumerable keys from a target. + * + * @param {Object} target + * @returns {Array} an array of own and inherited enumerable keys from the target. + */ + +/*! + * Inherit from Error.prototype + */ + +/*! + * Inspect util + */ + +/*! + * Module dependencies + */ + +/*! + * Object Display util + */ + +/*! + * Overwrite chainable method + */ + +/*! + * Primary Export + */ + +/*! + * Primary Exports + */ + +/*! + * Primary `Assertion` prototype + */ + +/*! + * Proxify util + */ + +/*! + * Recursively check the equality of two Objects. Once basic sameness has been established it will defer to `deepEqual` + * for each enumerable key in the object. + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +/*! + * Return a function that will copy properties from + * one object to another excluding any originally + * listed. Returned function will create a new `{}`. + * + * @param {String} excluded properties ... + * @return {Function} + */ + +/*! + * Returns true if the argument is a primitive. + * + * This intentionally returns true for all objects that can be compared by reference, + * including functions and symbols. + * + * @param {Mixed} value + * @return {Boolean} result + */ + +/*! + * Set the result of the equality into the MemoizeMap + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {MemoizeMap} memoizeMap + * @param {Boolean} result +*/ + +/*! + * Should interface + */ + +/*! + * Simple equality for flat iterable objects such as Arrays, TypedArrays or Node.js buffers. + * + * @param {Iterable} leftHandOperand + * @param {Iterable} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +/*! + * Simple equality for generator objects such as those returned by generator functions. + * + * @param {Iterable} leftHandOperand + * @param {Iterable} rightHandOperand + * @param {Object} [options] (Optional) + * @return {Boolean} result + */ + +/*! + * Statically set name + */ + +/*! + * The main logic of the `deepEqual` function. + * + * @param {Mixed} leftHandOperand + * @param {Mixed} rightHandOperand + * @param {Object} [options] (optional) Additional options + * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality. + * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of + complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular + references to blow the stack. + * @return {Boolean} equal match +*/ + +/*! + * Utility Functions + */ + +/*! + * Utils for plugins (not exported) + */ + +/*! + * actual utility + */ + +/*! + * add Method + */ + +/*! + * add Property + */ + +/*! + * addLengthGuard util + */ + +/*! + * assertion-error + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * chai + * Copyright(c) 2011 Jake Luer + * MIT Licensed + */ + +/*! + * chai + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +/*! + * chai + * http://chaijs.com + * Copyright(c) 2011-2014 Jake Luer + * MIT Licensed + */ + +/*! + * deep-eql + * Copyright(c) 2013 Jake Luer + * MIT Licensed + */ + +/*! + * expectTypes utility + */ + +/*! + * getOperator method + */ + +/*! + * isNaN method + */ + +/*! + * isProxyEnabled helper + */ + +/*! + * jQuery JavaScript Library v3.7.1 + * https://jquery.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2023-08-28T13:37Z + */ + +/*! + * message utility + */ + +/*! + * overwrite Method + */ + +/*! + * overwrite Property + */ + +/*! + * test utility + */ + +/*! + * type utility + */ diff --git a/mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js b/mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js new file mode 100644 index 000000000..5811e9cbd --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js @@ -0,0 +1 @@ +(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[111,645],{596(){},1784(){},22179(){},26127(){},62383(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js b/mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js new file mode 100644 index 000000000..5811e9cbd --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js @@ -0,0 +1 @@ +(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[111,645],{596(){},1784(){},22179(){},26127(){},62383(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js b/mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js new file mode 100644 index 000000000..dcb140c4e --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js @@ -0,0 +1 @@ +(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{23(){},2329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=2329,e.exports=t},4838(){},6509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>c});var n=r(7706);function i(e){return(0,n.createSourceFile)("inline.ts",e,n.ScriptTarget.Latest,!0,n.ScriptKind.TS)}function s(e){if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,n.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,n.isInterfaceDeclaration)(e)||(0,n.isTypeLiteralNode)(e)||(0,n.isClassDeclaration)(e)?e.members:(0,n.isTypeAliasDeclaration)(e)?(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,n.isPropertySignature)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isParameter)(e))&&e.type&&(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]}function a(e,t=n.SyntaxKind.Unknown){if(!e.trim())return null;let r;return t===n.SyntaxKind.MethodDeclaration?(r=i(`class _ { ${e} }`),r.statements[0].members.find((e=>(0,n.isMethodDeclaration)(e)))||null):t===n.SyntaxKind.TypeReference?(r=i(`let _: ${e};`),r.statements[0].declarationList.declarations[0].type||null):(r=i(e),r)}const o=e=>e.filter((({kind:e})=>e!==n.SyntaxKind.SemicolonToken)),l=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,n.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const i=o(e.getChildren()),s=o(t.getChildren());if(0===i.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(i.length!==s.length)return!1;for(let e=0;et.kind===e)).map((e=>new c(e)))}getVariables(){const e=this.getAll(n.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}getAnnotation(){if(this.isEmpty())return new c;const e=this.tree;if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new c(t.type)}return((0,n.isParameter)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isPropertySignature)(e)||(0,n.isTypeAliasDeclaration)(e))&&e.type?new c(e.type):new c}hasAnnotation(e){const t=this.getAnnotation();if(t.isEmpty())return!1;const r=a(e,n.SyntaxKind.TypeReference);if(null===r)return!1;const i=new c(r);return t.matches(i)}getFunctions(e=!1){const t={};return this.getAll(n.SyntaxKind.FunctionDeclaration).forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),e&&this.getAll(n.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,n.isArrowFunction)(t.initializer)||(0,n.isFunctionExpression)(t.initializer))})).forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,n.isFunctionDeclaration)(this.tree)||(0,n.isMethodDeclaration)(this.tree)||(0,n.isArrowFunction)(this.tree)||(0,n.isFunctionExpression)(this.tree))&&(t=this.tree),(0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,n.isArrowFunction)(e)||(0,n.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new c(t.type),i=new c(e,n.SyntaxKind.TypeReference);return r.matches(i)}return!1}getParameters(){if(!this.tree)return[];if((0,n.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new c(e)));if((0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,n.isArrowFunction)(e)||e&&(0,n.isFunctionExpression)(e))return e.parameters.map((e=>new c(e)))}return[]}getTypes(){const e=this.getAll(n.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getInterfaces(){const e=this.getAll(n.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getClasses(){const e=this.getAll(n.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}getMethods(){const e={};return this.tree&&(0,n.isClassDeclaration)(this.tree)&&this.getAll(n.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}getClassProps(){if(!this.tree||!(0,n.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(n.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,n=r.name.text;e[n]=new c(r)})),e}getTypeProps(){if(!this.tree)return{};const e={};return this.getAll(n.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}hasTypeProp(e,t,r){if(!this.tree)return!1;const i=s(this.tree);if(!i)return!1;const a=i.find((t=>!(!t.name||!(0,n.isIdentifier)(t.name))&&t.name.text===e));if(!a)return!1;if(void 0!==t){if(!(0,n.isPropertySignature)(a))return!1;if(!a.type)return!1;if(!new c(a.type).matches(new c(t,n.SyntaxKind.TypeReference)))return!1}return void 0===r||!(0,n.isPropertySignature)(a)||void 0!==a.questionToken===r}hasTypeProps(e){if(!this.tree)return!1;if(!((0,n.isInterfaceDeclaration)(this.tree)||(0,n.isTypeLiteralNode)(this.tree)||(0,n.isTypeAliasDeclaration)(this.tree)||(0,n.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.getTypeProps();return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(!new c(r.tree.type).matches(new c(e.type,n.SyntaxKind.TypeReference)))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}}},7331(){},8164(){},8234(){},9905(){},9997(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js b/mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js new file mode 100644 index 000000000..3e4b9b154 --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js @@ -0,0 +1 @@ +(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{4838(){},22329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=22329,e.exports=t},28164(){},37331(){},40023(){},79997(){},88234(){},89905(){},96509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>p});var i=r(77706);function n(e){return(0,i.createSourceFile)("inline.ts",e,i.ScriptTarget.Latest,!0,i.ScriptKind.TS)}function s(e){return(0,i.isSourceFile)(e)||(0,i.isBlock)(e)||(0,i.isModuleBlock)(e)||(0,i.isCaseOrDefaultClause)(e)?e.statements:[]}function a(e){if((0,i.isFunctionDeclaration)(e)||(0,i.isMethodDeclaration)(e)||(0,i.isFunctionExpression)(e)||(0,i.isArrowFunction)(e)||(0,i.isConstructorDeclaration)(e))return e.body;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];if(t&&((0,i.isArrowFunction)(t)||(0,i.isFunctionExpression)(t)))return t.body}}const o=[["constructor",[i.isConstructorDeclaration]],["method",[i.isMethodDeclaration]],["propertyDeclaration",[i.isPropertyDeclaration]],["parameter",[i.isParameter]],["typeParameter",[i.isTypeParameterDeclaration]],["typeReference",[i.isTypeReferenceNode,i.isUnionTypeNode,i.isIntersectionTypeNode,i.isArrayTypeNode,i.isTupleTypeNode,i.isFunctionTypeNode,i.isTypeLiteralNode,i.isLiteralTypeNode,i.isParenthesizedTypeNode,i.isConditionalTypeNode,i.isMappedTypeNode,i.isTemplateLiteralTypeNode,i.isIndexedAccessTypeNode,i.isTypeOperatorNode,i.isRestTypeNode,i.isOptionalTypeNode,i.isTypeNode]],["expression",[i.isObjectLiteralExpression,i.isArrayLiteralExpression,i.isNonNullExpression,i.isAsExpression,i.isPropertyAccessExpression,i.isBinaryExpression,i.isArrowFunction,i.isFunctionExpression,i.isExpression]]];function c(e,t){if(!e.trim())return null;switch(t){case"method":return n(`class _ { ${e} }`).statements[0].members.find(i.isMethodDeclaration)??null;case"constructor":return n(`class _ { ${e} }`).statements[0].members.find(i.isConstructorDeclaration)??null;case"parameter":return n(`function _(${e}) {}`).statements[0].parameters[0]??null;case"typeParameter":{const t=n(`function _<${e}>() {}`).statements[0];return t.typeParameters?.[0]??null}case"propertyDeclaration":return n(`class _ { ${e} }`).statements[0].members.find(i.isPropertyDeclaration)??null;case"typeReference":return n(`let _: ${e};`).statements[0].declarationList.declarations[0].type??null;case"expression":return n(`const _ = ${e};`).statements[0].declarationList.declarations[0].initializer??null;case"source":return n(e);default:return null}}const l=e=>e.filter((({kind:e})=>e!==i.SyntaxKind.SemicolonToken)),u=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,i.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const n=l(e.getChildren()),s=l(t.getChildren());if(0===n.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(n.length!==s.length)return!1;for(let e=0;et(e))))return t;return"source"}(e))}isEmpty(){return null===this.tree}toString(){return this.tree?this.tree.getText():"no ast"}matches(e){return"string"==typeof e?!this.tree&&!e.trim()||!!this.tree&&u(this.tree,c(e,this.context)):u(this.tree,e.tree)}getAll(e){if(!this.tree)return[];const t=a(this.tree)??this.tree;return[...function(e){if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,i.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,i.isInterfaceDeclaration)(e)||(0,i.isTypeLiteralNode)(e)||(0,i.isClassDeclaration)(e)?e.members:(0,i.isTypeAliasDeclaration)(e)?(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,i.isPropertySignature)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e))&&e.type&&(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]}(t),...s(t)].filter((t=>t.kind===e)).map((e=>new p(e)))}get variables(){const e=this.getAll(i.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}get value(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];return t?new p(t):new p}return(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e)?e.initializer?new p(e.initializer):new p:(0,i.isPropertyAssignment)(e)&&e.initializer?new p(e.initializer):new p}get annotation(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new p(t.type)}return((0,i.isParameter)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isPropertySignature)(e)||(0,i.isTypeAliasDeclaration)(e))&&e.type?new p(e.type):new p}isUnionOf(e){const t=this.annotation;if(t.isEmpty())return!1;const r=t.tree;if(!r||!(0,i.isUnionTypeNode)(r))return!1;const n=r.types;return n.length===e.length&&n.every((t=>e.some((e=>{const r=new p(e,"typeReference");return!r.isEmpty()&&u(t,r.tree)}))))}hasAnnotation(e){const t=this.annotation;if(t.isEmpty())return!1;const r=new p(e,"typeReference");return t.matches(r)}get functions(){const e={};return this.getAll(i.SyntaxKind.FunctionDeclaration).forEach((t=>{const r=t.tree.name?.text;r&&(e[r]=t)})),e}get allFunctions(){const e={...this.functions};return this.getAll(i.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,i.isArrowFunction)(t.initializer)||(0,i.isFunctionExpression)(t.initializer))})).forEach((t=>{const r=t.tree.declarationList.declarations[0].name.text;e[r]=t})),e}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))&&(t=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new p(t.type),i=new p(e,"typeReference");return r.matches(i)}return!1}get parameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new p(e)));if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,i.isArrowFunction)(e)||e&&(0,i.isFunctionExpression)(e))return e.parameters.map((e=>new p(e)))}return(0,i.isMethodDeclaration)(this.tree)?this.tree.parameters.map((e=>new p(e))):[]}get typeParameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isClassDeclaration)(this.tree)||(0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e)))return e.typeParameters?.map((e=>new p(e)))??[]}return[]}get typeArguments(){return this.tree&&((0,i.isTypeReferenceNode)(this.tree)||(0,i.isCallExpression)(this.tree)||(0,i.isNewExpression)(this.tree))?this.tree.typeArguments?.map((e=>new p(e)))??[]:[]}hasReturn(e){if(this.isEmpty())return!1;const t=a(this.tree);if(!t)return!1;if((0,i.isBlock)(t)){const r=s(t);for(const t of r)if(t.kind===i.SyntaxKind.ReturnStatement){const r=t;if(r.expression)return new p(r.expression).matches(e)}return!1}return new p(t).matches(e)}get types(){const e=this.getAll(i.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get interfaces(){const e=this.getAll(i.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get classes(){const e=this.getAll(i.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}get methods(){const e={};return this.tree&&(0,i.isClassDeclaration)(this.tree)&&this.getAll(i.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get classConstructor(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return null;const e=this.getAll(i.SyntaxKind.Constructor);return e.length>0?e[0]:null}get classProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(i.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,i=r.name.text;e[i]=new p(r)})),e}get constructorProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e=this.classConstructor;if(!e||e.isEmpty())return{};const t=a(e.tree);if(!t||!(0,i.isBlock)(t))return{};const r={};return t.statements.forEach((e=>{if((0,i.isExpressionStatement)(e)){const t=e.expression;if((0,i.isBinaryExpression)(t)&&(0,i.isPropertyAccessExpression)(t.left)){const n=t.left;if(n.expression.kind===i.SyntaxKind.ThisKeyword&&(0,i.isIdentifier)(n.name)){const t=n.name.text;t in r||(r[t]=new p(e))}}}})),r}get typeProps(){if(!this.tree)return{};const e={};return this.getAll(i.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get objectProps(){if(!this.tree)return{};let e;if((0,i.isObjectLiteralExpression)(this.tree)&&(e=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:t}=this.tree.declarationList.declarations[0];t&&(0,i.isObjectLiteralExpression)(t)&&(e=t)}if(!e)return{};const t={};return e.properties.forEach((e=>{if((0,i.isPropertyAssignment)(e)&&e.name&&(0,i.isIdentifier)(e.name)){const r=e.name.text;t[r]=new p(e)}})),t}hasTypeProps(e){if(!this.tree)return!1;if(!((0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeLiteralNode)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree)||(0,i.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.typeProps;return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(!new p(r.tree.type).matches(new p(e.type,"typeReference")))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}hasCast(e){if(this.isEmpty())return!1;const t=this.tree;if((0,i.isAsExpression)(t)){if(void 0===e)return!0;const r=t.type;if(r){const t=new p(r),i=new p(e,"typeReference");return t.matches(i)}return!1}return!1}doesExtend(e){if(!this.tree)return!1;const t=this.tree,r=((0,i.isClassDeclaration)(t)||(0,i.isInterfaceDeclaration)(t))&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ExtendsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}doesImplement(e){if(!this.tree)return!1;const t=this.tree,r=(0,i.isClassDeclaration)(t)&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ImplementsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}isPrivate(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PrivateKeyword))??!1)}isProtected(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ProtectedKeyword))??!1)}isPublic(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PublicKeyword))??!1)}isReadOnly(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ReadonlyKeyword))??!1)}hasNonNullAssertion(){return!!this.tree&&(0,i.isNonNullExpression)(this.tree)}}}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js b/mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js new file mode 100644 index 000000000..e21d43ebd --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js @@ -0,0 +1 @@ +(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{23(){},2329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=2329,e.exports=t},4838(){},6509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>p});var i=r(7706);function n(e){return(0,i.createSourceFile)("inline.ts",e,i.ScriptTarget.Latest,!0,i.ScriptKind.TS)}function s(e){return(0,i.isSourceFile)(e)||(0,i.isBlock)(e)||(0,i.isModuleBlock)(e)||(0,i.isCaseOrDefaultClause)(e)?e.statements:[]}function a(e){if((0,i.isFunctionDeclaration)(e)||(0,i.isMethodDeclaration)(e)||(0,i.isFunctionExpression)(e)||(0,i.isArrowFunction)(e)||(0,i.isConstructorDeclaration)(e))return e.body;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];if(t&&((0,i.isArrowFunction)(t)||(0,i.isFunctionExpression)(t)))return t.body}}const o=[["constructor",[i.isConstructorDeclaration]],["method",[i.isMethodDeclaration]],["propertyDeclaration",[i.isPropertyDeclaration]],["parameter",[i.isParameter]],["typeParameter",[i.isTypeParameterDeclaration]],["typeReference",[i.isTypeReferenceNode,i.isUnionTypeNode,i.isIntersectionTypeNode,i.isArrayTypeNode,i.isTupleTypeNode,i.isFunctionTypeNode,i.isTypeLiteralNode,i.isLiteralTypeNode,i.isParenthesizedTypeNode,i.isConditionalTypeNode,i.isMappedTypeNode,i.isTemplateLiteralTypeNode,i.isIndexedAccessTypeNode,i.isTypeOperatorNode,i.isRestTypeNode,i.isOptionalTypeNode,i.isTypeNode]],["expression",[i.isObjectLiteralExpression,i.isArrayLiteralExpression,i.isNonNullExpression,i.isAsExpression,i.isPropertyAccessExpression,i.isBinaryExpression,i.isArrowFunction,i.isFunctionExpression,i.isExpression]]];function c(e,t){if(!e.trim())return null;switch(t){case"method":return n(`class _ { ${e} }`).statements[0].members.find(i.isMethodDeclaration)??null;case"constructor":return n(`class _ { ${e} }`).statements[0].members.find(i.isConstructorDeclaration)??null;case"parameter":return n(`function _(${e}) {}`).statements[0].parameters[0]??null;case"typeParameter":{const t=n(`function _<${e}>() {}`).statements[0];return t.typeParameters?.[0]??null}case"propertyDeclaration":return n(`class _ { ${e} }`).statements[0].members.find(i.isPropertyDeclaration)??null;case"typeReference":return n(`let _: ${e};`).statements[0].declarationList.declarations[0].type??null;case"expression":return n(`const _ = ${e};`).statements[0].declarationList.declarations[0].initializer??null;case"source":return n(e);default:return null}}const l=e=>e.filter((({kind:e})=>e!==i.SyntaxKind.SemicolonToken)),u=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,i.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const n=l(e.getChildren()),s=l(t.getChildren());if(0===n.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(n.length!==s.length)return!1;for(let e=0;et(e))))return t;return"source"}(e))}isEmpty(){return null===this.tree}toString(){return this.tree?this.tree.getText():"no ast"}matches(e){return"string"==typeof e?!this.tree&&!e.trim()||!!this.tree&&u(this.tree,c(e,this.context)):u(this.tree,e.tree)}getAll(e){if(!this.tree)return[];const t=a(this.tree)??this.tree;return[...function(e){if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,i.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,i.isInterfaceDeclaration)(e)||(0,i.isTypeLiteralNode)(e)||(0,i.isClassDeclaration)(e)?e.members:(0,i.isTypeAliasDeclaration)(e)?(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,i.isPropertySignature)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e))&&e.type&&(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]}(t),...s(t)].filter((t=>t.kind===e)).map((e=>new p(e)))}get variables(){const e=this.getAll(i.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}get value(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];return t?new p(t):new p}return(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e)?e.initializer?new p(e.initializer):new p:(0,i.isPropertyAssignment)(e)&&e.initializer?new p(e.initializer):new p}get annotation(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new p(t.type)}return((0,i.isParameter)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isPropertySignature)(e)||(0,i.isTypeAliasDeclaration)(e))&&e.type?new p(e.type):new p}isUnionOf(e){const t=this.annotation;if(t.isEmpty())return!1;const r=t.tree;if(!r||!(0,i.isUnionTypeNode)(r))return!1;const n=r.types;return n.length===e.length&&n.every((t=>e.some((e=>{const r=new p(e,"typeReference");return!r.isEmpty()&&u(t,r.tree)}))))}hasAnnotation(e){const t=this.annotation;if(t.isEmpty())return!1;const r=new p(e,"typeReference");return t.matches(r)}get functions(){const e={};return this.getAll(i.SyntaxKind.FunctionDeclaration).forEach((t=>{const r=t.tree.name?.text;r&&(e[r]=t)})),e}get allFunctions(){const e={...this.functions};return this.getAll(i.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,i.isArrowFunction)(t.initializer)||(0,i.isFunctionExpression)(t.initializer))})).forEach((t=>{const r=t.tree.declarationList.declarations[0].name.text;e[r]=t})),e}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))&&(t=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new p(t.type),i=new p(e,"typeReference");return r.matches(i)}return!1}get parameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new p(e)));if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,i.isArrowFunction)(e)||e&&(0,i.isFunctionExpression)(e))return e.parameters.map((e=>new p(e)))}return(0,i.isMethodDeclaration)(this.tree)?this.tree.parameters.map((e=>new p(e))):[]}get typeParameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isClassDeclaration)(this.tree)||(0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e)))return e.typeParameters?.map((e=>new p(e)))??[]}return[]}get typeArguments(){return this.tree&&((0,i.isTypeReferenceNode)(this.tree)||(0,i.isCallExpression)(this.tree)||(0,i.isNewExpression)(this.tree))?this.tree.typeArguments?.map((e=>new p(e)))??[]:[]}hasReturn(e){if(this.isEmpty())return!1;const t=a(this.tree);if(!t)return!1;if((0,i.isBlock)(t)){const r=s(t);for(const t of r)if(t.kind===i.SyntaxKind.ReturnStatement){const r=t;if(r.expression)return new p(r.expression).matches(e)}return!1}return new p(t).matches(e)}get types(){const e=this.getAll(i.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get interfaces(){const e=this.getAll(i.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get classes(){const e=this.getAll(i.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}get methods(){const e={};return this.tree&&(0,i.isClassDeclaration)(this.tree)&&this.getAll(i.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get classConstructor(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return null;const e=this.getAll(i.SyntaxKind.Constructor);return e.length>0?e[0]:null}get classProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(i.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,i=r.name.text;e[i]=new p(r)})),e}get constructorProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e=this.classConstructor;if(!e||e.isEmpty())return{};const t=a(e.tree);if(!t||!(0,i.isBlock)(t))return{};const r={};return t.statements.forEach((e=>{if((0,i.isExpressionStatement)(e)){const t=e.expression;if((0,i.isBinaryExpression)(t)&&(0,i.isPropertyAccessExpression)(t.left)){const n=t.left;if(n.expression.kind===i.SyntaxKind.ThisKeyword&&(0,i.isIdentifier)(n.name)){const t=n.name.text;t in r||(r[t]=new p(e))}}}})),r}get typeProps(){if(!this.tree)return{};const e={};return this.getAll(i.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get objectProps(){if(!this.tree)return{};let e;if((0,i.isObjectLiteralExpression)(this.tree)&&(e=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:t}=this.tree.declarationList.declarations[0];t&&(0,i.isObjectLiteralExpression)(t)&&(e=t)}if(!e)return{};const t={};return e.properties.forEach((e=>{if((0,i.isPropertyAssignment)(e)&&e.name&&(0,i.isIdentifier)(e.name)){const r=e.name.text;t[r]=new p(e)}})),t}hasTypeProps(e){if(!this.tree)return!1;if(!((0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeLiteralNode)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree)||(0,i.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.typeProps;return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(!new p(r.tree.type).matches(new p(e.type,"typeReference")))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}hasCast(e){if(this.isEmpty())return!1;const t=this.tree;if((0,i.isAsExpression)(t)){if(void 0===e)return!0;const r=t.type;if(r){const t=new p(r),i=new p(e,"typeReference");return t.matches(i)}return!1}return!1}doesExtend(e){if(!this.tree)return!1;const t=this.tree,r=((0,i.isClassDeclaration)(t)||(0,i.isInterfaceDeclaration)(t))&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ExtendsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}doesImplement(e){if(!this.tree)return!1;const t=this.tree,r=(0,i.isClassDeclaration)(t)&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ImplementsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}isPrivate(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PrivateKeyword))??!1)}isProtected(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ProtectedKeyword))??!1)}isPublic(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PublicKeyword))??!1)}isReadOnly(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ReadonlyKeyword))??!1)}hasNonNullAssertion(){return!!this.tree&&(0,i.isNonNullExpression)(this.tree)}}},7331(){},8164(){},8234(){},9905(){},9997(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js b/mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js new file mode 100644 index 000000000..947a91a19 --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js @@ -0,0 +1 @@ +(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{4838(){},22329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=22329,e.exports=t},28164(){},37331(){},40023(){},79997(){},88234(){},89905(){},96509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>c});var n=r(77706);function i(e){return(0,n.createSourceFile)("inline.ts",e,n.ScriptTarget.Latest,!0,n.ScriptKind.TS)}function s(e){if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,n.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,n.isInterfaceDeclaration)(e)||(0,n.isTypeLiteralNode)(e)||(0,n.isClassDeclaration)(e)?e.members:(0,n.isTypeAliasDeclaration)(e)?(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,n.isPropertySignature)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isParameter)(e))&&e.type&&(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]}function a(e,t=n.SyntaxKind.Unknown){if(!e.trim())return null;let r;return t===n.SyntaxKind.MethodDeclaration?(r=i(`class _ { ${e} }`),r.statements[0].members.find((e=>(0,n.isMethodDeclaration)(e)))||null):t===n.SyntaxKind.TypeReference?(r=i(`let _: ${e};`),r.statements[0].declarationList.declarations[0].type||null):(r=i(e),r)}const o=e=>e.filter((({kind:e})=>e!==n.SyntaxKind.SemicolonToken)),l=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,n.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const i=o(e.getChildren()),s=o(t.getChildren());if(0===i.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(i.length!==s.length)return!1;for(let e=0;et.kind===e)).map((e=>new c(e)))}getVariables(){const e=this.getAll(n.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}getAnnotation(){if(this.isEmpty())return new c;const e=this.tree;if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new c(t.type)}return((0,n.isParameter)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isPropertySignature)(e)||(0,n.isTypeAliasDeclaration)(e))&&e.type?new c(e.type):new c}hasAnnotation(e){const t=this.getAnnotation();if(t.isEmpty())return!1;const r=a(e,n.SyntaxKind.TypeReference);if(null===r)return!1;const i=new c(r);return t.matches(i)}getFunctions(e=!1){const t={};return this.getAll(n.SyntaxKind.FunctionDeclaration).forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),e&&this.getAll(n.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,n.isArrowFunction)(t.initializer)||(0,n.isFunctionExpression)(t.initializer))})).forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,n.isFunctionDeclaration)(this.tree)||(0,n.isMethodDeclaration)(this.tree)||(0,n.isArrowFunction)(this.tree)||(0,n.isFunctionExpression)(this.tree))&&(t=this.tree),(0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,n.isArrowFunction)(e)||(0,n.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new c(t.type),i=new c(e,n.SyntaxKind.TypeReference);return r.matches(i)}return!1}getParameters(){if(!this.tree)return[];if((0,n.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new c(e)));if((0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,n.isArrowFunction)(e)||e&&(0,n.isFunctionExpression)(e))return e.parameters.map((e=>new c(e)))}return[]}getTypes(){const e=this.getAll(n.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getInterfaces(){const e=this.getAll(n.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getClasses(){const e=this.getAll(n.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}getMethods(){const e={};return this.tree&&(0,n.isClassDeclaration)(this.tree)&&this.getAll(n.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}getClassProps(){if(!this.tree||!(0,n.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(n.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,n=r.name.text;e[n]=new c(r)})),e}getTypeProps(){if(!this.tree)return{};const e={};return this.getAll(n.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}hasTypeProp(e,t,r){if(!this.tree)return!1;const i=s(this.tree);if(!i)return!1;const a=i.find((t=>!(!t.name||!(0,n.isIdentifier)(t.name))&&t.name.text===e));if(!a)return!1;if(void 0!==t){if(!(0,n.isPropertySignature)(a))return!1;if(!a.type)return!1;if(!new c(a.type).matches(new c(t,n.SyntaxKind.TypeReference)))return!1}return void 0===r||!(0,n.isPropertySignature)(a)||void 0!==a.questionToken===r}hasTypeProps(e){if(!this.tree)return!1;if(!((0,n.isInterfaceDeclaration)(this.tree)||(0,n.isTypeLiteralNode)(this.tree)||(0,n.isTypeAliasDeclaration)(this.tree)||(0,n.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.getTypeProps();return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(!new c(r.tree.type).matches(new c(e.type,n.SyntaxKind.TypeReference)))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}}}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/index.js b/mobile-app-new/assets/test_runner/dist/index.js new file mode 100644 index 000000000..fb91fadbe --- /dev/null +++ b/mobile-app-new/assets/test_runner/dist/index.js @@ -0,0 +1 @@ +(()=>{"use strict";function t({messenger:t,message:e}){return new Promise((s=>{const r=new MessageChannel;r.port1.onmessage=t=>{r.port1.close(),s(t.data)},t.postMessage(e,{targetOrigin:"*",transfer:[r.port2]})}))}const e=(t="/dist/")=>{const e=t.startsWith("/"),s=t.endsWith("/");return e||(t="/"+t),s||(t+="/"),t},s=t=>{if("fetch"===t.data.type){const{url:e,options:s}=t.data;fetch(e,{...s,credentials:"omit"}).then((async e=>{const s=await e.text();t.ports[0].postMessage({status:e.status,statusText:e.statusText,url:e.url,text:s})}))}};class r{#t;#e;#s;#r({assetPath:t,script:s}){const r=document.createElement("iframe");return r.sandbox.add("allow-scripts","allow-forms"),r.allow="autoplay",r.id="test-frame",{iframe:r,scriptHTML:` - - - '''; - bool value = await service.jsFileIsLinked(document, 'script.js'); - expect(value, false); - }); - - testWidgets( - 'it should return true if the JS file is linked in the HTML document', - (tester) async { - String document = ''' - - - - - - - '''; - bool value = await service.jsFileIsLinked(document, 'script.js'); - expect(value, true); - }); - - testWidgets( - 'it should return true if the JS file is in a folder and is linked in the HTML document', - (tester) async { - String document = ''' - - - - - - - '''; - bool value = await service.jsFileIsLinked(document, 'script.js'); - expect(value, true); - }); - }); - - group('parseCssDocumentsAsStyleTags function', () { - testWidgets('it should inject style tag if CSS file is linked', - (tester) async { - Challenge cssChallenge = Challenge( - id: '2', - block: 'css block', - title: 'css-test', - description: '', - instructions: '', - transcript: '', - dashedName: 'css-test', - superBlock: 'super block', - challengeType: 1, - helpCategory: HelpCategory.htmlCss, - tests: [], - hooks: Hooks(beforeAll: '', beforeEach: '', afterEach: ''), - files: [ - ChallengeFile( - ext: Ext.html, - name: 'index', - editableRegionBoundaries: [], - contents: - '''''', - history: [], - fileKey: 'index.html', - ), - ChallengeFile( - ext: Ext.css, - name: 'styles', - editableRegionBoundaries: [1, 5], - contents: 'body { color: red; }', - history: [], - fileKey: 'styles.css', - ) - ], - ); - String html = cssChallenge.files[0].contents; - String result = await service - .parseCssDocumentsAsStyleTags(cssChallenge, html, testing: true); - expect( - result, - contains( - '')); - }); - - testWidgets('it should not inject style tag if CSS file is not linked', - (tester) async { - Challenge cssChallenge = Challenge( - id: '3', - block: 'css block', - title: 'css-test', - description: '', - instructions: '', - transcript: '', - dashedName: 'css-test', - superBlock: 'super block', - challengeType: 1, - helpCategory: HelpCategory.htmlCss, - tests: [], - hooks: Hooks(beforeAll: '', beforeEach: '', afterEach: ''), - files: [ - ChallengeFile( - ext: Ext.html, - name: 'index', - editableRegionBoundaries: [], - contents: '''''', - history: [], - fileKey: 'index.html', - ), - ChallengeFile( - ext: Ext.css, - name: 'styles', - editableRegionBoundaries: [1, 5], - contents: 'body { color: blue; }', - history: [], - fileKey: 'styles.css', - ) - ], - ); - String html = cssChallenge.files[0].contents; - String result = await service - .parseCssDocumentsAsStyleTags(cssChallenge, html, testing: true); - expect(result, isNot(contains('fcc-injected-styles'))); - }); - }); - - group('parseJsDocumentsAsScriptTags function', () { - testWidgets( - 'it should throw an error if JS file is linked when babelController is null', - (tester) async { - Challenge jsChallenge = Challenge( - id: '4', - block: 'js block', - title: 'js-test', - description: '', - instructions: '', - transcript: '', - dashedName: 'js-test', - superBlock: 'super block', - challengeType: 1, - helpCategory: HelpCategory.htmlCss, - tests: [], - hooks: Hooks(beforeAll: '', beforeEach: '', afterEach: ''), - files: [ - ChallengeFile( - ext: Ext.html, - name: 'index', - editableRegionBoundaries: [], - contents: - '''''', - history: [], - fileKey: 'index.html', - ), - ChallengeFile( - ext: Ext.js, - name: 'script', - editableRegionBoundaries: [1, 5], - contents: 'console.log("hello");', - history: [], - fileKey: 'script.js', - ) - ], - ); - String html = jsChallenge.files[0].contents; - expect( - () async => await service.parseJsDocumentsAsScriptTags(jsChallenge, html, null, testing: true), - throwsA(isA().having((e) => e.toString(), 'description', contains('Babel controller is required to transpile JS code.'))), - ); - }); - }); - - group('changeActiveFileLinks function', () { - test('it should replace href/src with data-href/data-src for known files', - () { - String html = ''' - - - - - - - - '''; - - const expectedHtml = ''' - - - - - - - - '''; - - String result = service.changeActiveFileLinks(html); - String stripWhitespace(String s) => s.replaceAll(RegExp(r'\s+'), ''); - - expect(stripWhitespace(result), stripWhitespace(expectedHtml)); - }); - - test('it should not change unrelated links/scripts', () { - String html = ''' - - - - - - - - '''; - - String result = service.changeActiveFileLinks(html); - String stripWhitespace(String s) => s.replaceAll(RegExp(r'\s+'), ''); - - expect(stripWhitespace(result), stripWhitespace(html)); - }); - }); -} diff --git a/mobile-app/test/widget/daily_challenge_card_test.dart b/mobile-app/test/widget/daily_challenge_card_test.dart deleted file mode 100644 index 6d09e7ca8..000000000 --- a/mobile-app/test/widget/daily_challenge_card_test.dart +++ /dev/null @@ -1,216 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:freecodecamp/app/app.locator.dart'; -import 'package:freecodecamp/app/app.router.dart'; -import 'package:freecodecamp/l10n/app_localizations.dart'; -import 'package:freecodecamp/models/learn/daily_challenge_model.dart'; -import 'package:freecodecamp/ui/views/learn/widgets/daily_challenge_card.dart'; -import 'package:mockito/mockito.dart'; -import 'package:timezone/data/latest.dart' as tzdata; - -import '../helpers/test_helpers.dart'; -import '../helpers/test_helpers.mocks.dart'; - -void main() { - // Mock service variables - late MockNavigationService mockNavigationService; - - // Test data - final testChallenge = DailyChallenge( - id: 'test-challenge-id', - challengeNumber: 1, - date: DateTime(2025, 7, 26), - title: 'Test Daily Challenge', - description: 'A test challenge description', - javascript: DailyChallengeLanguageData( - tests: [], - challengeFiles: [], - ), - python: DailyChallengeLanguageData( - tests: [], - challengeFiles: [], - ), - ); - - setUp(() { - TestWidgetsFlutterBinding.ensureInitialized(); - tzdata.initializeTimeZones(); - - // Register services - registerServices(); - mockNavigationService = getAndRegisterNavigationService(); - }); - - tearDown(() { - locator.reset(); - }); - - Widget createTestWidget({ - DailyChallenge? challenge, - bool isCompleted = false, - }) { - return MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - home: Scaffold( - body: DailyChallengeCard( - dailyChallenge: challenge, - isCompleted: isCompleted, - ), - ), - onGenerateRoute: (settings) { - // Handle navigation routes used by the widget - if (settings.name == '/challenge-template-view') { - return MaterialPageRoute( - builder: (context) => Scaffold( - body: Center(child: Text('Challenge Template View')), - ), - ); - } - return null; - }, - ); - } - - testWidgets('should return empty widget when no challenge is provided', - (WidgetTester tester) async { - await tester.pumpWidget(createTestWidget()); - - expect(find.byType(DailyChallengeCard), findsOneWidget); - expect(find.text('Today\'s challenge'), findsNothing); - expect(find.text('Start the challenge'), findsNothing); - }); - - testWidgets( - 'should show challenge details when challenge is provided and not completed', - (WidgetTester tester) async { - await tester.pumpWidget(createTestWidget( - challenge: testChallenge, - isCompleted: false, - )); - - expect(find.text('Today\'s challenge'), findsOneWidget); - expect(find.text('Test Daily Challenge'), findsOneWidget); - expect(find.text('Do you have the skills to complete this challenge?'), - findsOneWidget); - expect(find.text('Start the challenge'), findsOneWidget); - expect(find.byIcon(Icons.arrow_forward_ios), findsOneWidget); - - // Test navigation to the challenge - await tester.tap(find.text('Start the challenge')); - await tester.pumpAndSettle(); - expect(find.text('Challenge Template View'), findsOneWidget); - }); - - testWidgets( - 'should show completed state with timer and allow navigation to past challenges', - (WidgetTester tester) async { - await tester.pumpWidget(createTestWidget( - challenge: testChallenge, - isCompleted: true, - )); - - expect(find.text('Today\'s challenge completed!'), findsOneWidget); - expect(find.textContaining('Next challenge in:'), findsOneWidget); - expect(find.text('View past challenges'), findsOneWidget); - expect(find.byIcon(Icons.history), findsOneWidget); - - final timerRegex = RegExp(r'^Next challenge in: \d{2}:\d{2}:\d{2}$'); - final timerFinder = find.byWidgetPredicate((widget) { - if (widget is Text) { - return timerRegex.hasMatch(widget.data ?? ''); - } - return false; - }); - expect(timerFinder, findsOneWidget); - - // Test navigation to past challenges - await tester.tap(find.text('View past challenges')); - await tester.pumpAndSettle(); - verify(mockNavigationService.navigateTo(Routes.dailyChallengeView)) - .called(1); - }); - - testWidgets( - 'should have proper accessibility semantics for not completed state', - (WidgetTester tester) async { - await tester.pumpWidget(createTestWidget( - challenge: testChallenge, - isCompleted: false, - )); - - final cardSemanticsFinder = find.byElementPredicate((element) { - return element.widget is Semantics && - (element.widget as Semantics).properties.label == - 'Daily challenge card'; - }); - - final buttonSemanticsFinder = find.byElementPredicate((element) { - return element.widget is Semantics && - (element.widget as Semantics).properties.label == 'Go to challenge'; - }); - - expect(cardSemanticsFinder, findsOneWidget); - expect(buttonSemanticsFinder, findsOneWidget); - }); - - testWidgets('should have proper accessibility semantics for completed state', - (WidgetTester tester) async { - await tester.pumpWidget(createTestWidget( - challenge: testChallenge, - isCompleted: true, - )); - - final cardSemanticsFinder = find.byElementPredicate((element) { - return element.widget is Semantics && - (element.widget as Semantics).properties.label == - 'Daily challenge completed. View past challenges.'; - }); - - expect(cardSemanticsFinder, findsOneWidget); - }); - - testWidgets('should have live region semantics for timer in completed state', - (WidgetTester tester) async { - await tester.pumpWidget(createTestWidget( - challenge: testChallenge, - isCompleted: true, - )); - - final semanticsWidgets = - tester.widgetList(find.byType(Semantics)); - bool hasLiveRegion = false; - - for (final semantics in semanticsWidgets) { - if (semantics.properties.liveRegion == true) { - hasLiveRegion = true; - break; - } - } - - expect(hasLiveRegion, isTrue, - reason: 'Expected to find a Semantics widget with liveRegion: true'); - }); - - testWidgets('should display countdown timer in completed state', - (WidgetTester tester) async { - await tester.pumpWidget(createTestWidget( - challenge: testChallenge, - isCompleted: true, - )); - - final timerRegex = RegExp(r'^Next challenge in: \d{2}:\d{2}:\d{2}$'); - final timerFinder = find.byWidgetPredicate((widget) { - if (widget is Text) { - return timerRegex.hasMatch(widget.data ?? ''); - } - return false; - }); - - expect(timerFinder, findsOneWidget); - - // Check that timer text contains expected format - final timerWidget = tester.widget(timerFinder); - expect(timerWidget.data, matches(timerRegex)); - }); -} diff --git a/mobile-app/test/widget/daily_challenge_view_test.dart b/mobile-app/test/widget/daily_challenge_view_test.dart deleted file mode 100644 index 3b8a218e4..000000000 --- a/mobile-app/test/widget/daily_challenge_view_test.dart +++ /dev/null @@ -1,372 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:freecodecamp/app/app.locator.dart'; -import 'package:freecodecamp/l10n/app_localizations.dart'; -import 'package:freecodecamp/models/learn/daily_challenge_model.dart'; -import 'package:freecodecamp/models/main/user_model.dart'; -import 'package:freecodecamp/service/authentication/authentication_service.dart'; -import 'package:freecodecamp/service/learn/daily_challenge_service.dart'; -import 'package:freecodecamp/ui/views/learn/daily_challenge/daily_challenge_view.dart'; -import 'package:mockito/mockito.dart'; - -import '../helpers/test_helpers.dart'; -import '../helpers/test_helpers.mocks.dart'; - -void main() { - group('DailyChallengeView Widget Tests', () { - late MockDailyChallengeService mockDailyChallengeService; - late MockAuthenticationService mockAuthService; - - setUp(() { - registerServices(); - - mockDailyChallengeService = - locator() as MockDailyChallengeService; - mockAuthService = - locator() as MockAuthenticationService; - - when(mockDailyChallengeService.fetchAllDailyChallenges()) - .thenAnswer((_) async => []); - - when(mockAuthService.progress) - .thenAnswer((_) => StreamController()); - - mockAuthService.userModel = null; - }); - - tearDown(() { - locator.reset(); - }); - - Widget createTestWidget() { - return const MaterialApp( - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - home: DailyChallengeView(), - ); - } - - testWidgets('should display app bar with correct title', (tester) async { - await tester.pumpWidget(createTestWidget()); - - await tester.pump(); - - expect(find.byType(AppBar), findsOneWidget); - expect(find.text('Daily Coding Challenges'), findsOneWidget); - }); - - testWidgets('should show empty state when no challenges', (tester) async { - await tester.pumpWidget(createTestWidget()); - - await tester.pumpAndSettle(); - - expect( - find.text('No challenges available at the moment.'), findsOneWidget); - }); - - testWidgets('should display challenges when service returns data', - (tester) async { - reset(mockDailyChallengeService); - reset(mockAuthService); - - when(mockAuthService.progress) - .thenAnswer((_) => StreamController()); - - mockAuthService.userModel = null; - - final testChallenges = [ - // March 2025 challenges - DailyChallengeOverview( - id: 'mar-1', - challengeNumber: 3, - date: DateTime(2025, 3, 15), - title: 'March Challenge 3', - ), - DailyChallengeOverview( - id: 'mar-2', - challengeNumber: 1, - date: DateTime(2025, 3, 1), - title: 'March Challenge 1', - ), - DailyChallengeOverview( - id: 'mar-3', - challengeNumber: 2, - date: DateTime(2025, 3, 10), - title: 'March Challenge 2', - ), - - // February 2025 challenges - DailyChallengeOverview( - id: 'feb-1', - challengeNumber: 1, - date: DateTime(2025, 2, 1), - title: 'February Challenge 1', - ), - DailyChallengeOverview( - id: 'feb-2', - challengeNumber: 2, - date: DateTime(2025, 2, 15), - title: 'February Challenge 2', - ), - ]; - - when(mockDailyChallengeService.fetchAllDailyChallenges()) - .thenAnswer((_) async => testChallenges); - - await tester.pumpWidget(createTestWidget()); - await tester.pumpAndSettle(); - - expect(find.text('March 2025'), findsOneWidget); - expect(find.text('February 2025'), findsOneWidget); - - // Test month ordering: March should appear first (newest) - final marchPosition = tester.getTopLeft(find.text('March 2025')); - final februaryPosition = tester.getTopLeft(find.text('February 2025')); - - expect(marchPosition.dy < februaryPosition.dy, isTrue, - reason: 'March should appear before February'); - - // March block is open by default and its challenges are visible - expect(find.text('Challenge 3: March Challenge 3'), findsOneWidget); - expect(find.text('Challenge 2: March Challenge 2'), findsOneWidget); - expect(find.text('Challenge 1: March Challenge 1'), findsOneWidget); - - // Scroll down to make the February block fully visible - // Target the main ListView by finding the one that contains the month headers - final mainListView = find.ancestor( - of: find.text('March 2025'), - matching: find.byType(ListView), - ); - await tester.drag(mainListView, const Offset(0, -300)); - await tester.pumpAndSettle(); - - // Find the "Show Challenges" button for February - final showFebButton = find.text('Show Challenges'); - expect(showFebButton, findsOneWidget); - - // Tap the button to reveal February's challenges - await tester.tap(showFebButton); - await tester.pumpAndSettle(); - - // Assert that February's challenges are now visible - expect(find.text('Challenge 2: February Challenge 2'), findsOneWidget); - expect(find.text('Challenge 1: February Challenge 1'), findsOneWidget); - }); - - testWidgets( - 'should order challenges within month by challengeNumber descending', - (tester) async { - reset(mockDailyChallengeService); - reset(mockAuthService); - - when(mockAuthService.progress) - .thenAnswer((_) => StreamController()); - - mockAuthService.userModel = null; - - final testChallenges = [ - DailyChallengeOverview( - id: 'challenge-1', - challengeNumber: 1, - date: DateTime(2025, 2, 1), - title: 'Challenge 1', - ), - DailyChallengeOverview( - id: 'challenge-3', - challengeNumber: 3, - date: DateTime(2025, 2, 15), - title: 'Challenge 3', - ), - DailyChallengeOverview( - id: 'challenge-2', - challengeNumber: 2, - date: DateTime(2025, 2, 10), - title: 'Challenge 2', - ), - ]; - - when(mockDailyChallengeService.fetchAllDailyChallenges()) - .thenAnswer((_) async => testChallenges); - - await tester.pumpWidget(createTestWidget()); - await tester.pumpAndSettle(); - - // Verify the ordering by checking vertical positions - final challenge3Position = - tester.getTopLeft(find.text('Challenge 3: Challenge 3')); - final challenge2Position = - tester.getTopLeft(find.text('Challenge 2: Challenge 2')); - final challenge1Position = - tester.getTopLeft(find.text('Challenge 1: Challenge 1')); - - expect(challenge3Position.dy < challenge2Position.dy, isTrue, - reason: 'Challenge 3 should appear before Challenge 2'); - expect(challenge2Position.dy < challenge1Position.dy, isTrue, - reason: 'Challenge 2 should appear before Challenge 1'); - }); - - testWidgets('should show completion state when user is logged in', - (tester) async { - reset(mockDailyChallengeService); - reset(mockAuthService); - - when(mockAuthService.progress) - .thenAnswer((_) => StreamController()); - - final testChallenges = [ - DailyChallengeOverview( - id: 'challenge-1', - challengeNumber: 1, - date: DateTime(2025, 2, 1), - title: 'Foo', - ), - DailyChallengeOverview( - id: 'challenge-2', - challengeNumber: 2, - date: DateTime(2025, 2, 2), - title: 'Bar', - ), - ]; - - final testUserJson = { - 'id': 'test-user-id', - 'email': 'test@example.com', - 'username': 'testuser', - 'name': 'Test User', - 'picture': '', - 'currentChallengeId': '', - 'githubProfile': '', - 'linkedin': '', - 'twitter': '', - 'website': '', - 'location': '', - 'about': '', - 'emailVerified': true, - 'isEmailVerified': true, - 'acceptedPrivacyTerms': true, - 'sendQuincyEmail': false, - 'isCheater': false, - 'isDonating': false, - 'isHonest': true, - 'isFrontEndCert': false, - 'isDataVisCert': false, - 'isBackEndCert': false, - 'isFullStackCert': false, - 'isRespWebDesignCert': false, - 'is2018DataVisCert': false, - 'isFrontEndLibsCert': false, - 'isJsAlgoDataStructCert': false, - 'isApisMicroservicesCert': false, - 'isInfosecQaCert': false, - 'isQaCertV7': false, - 'isInfosecCertV7': false, - 'is2018FullStackCert': false, - 'isSciCompPyCertV7': false, - 'isDataAnalysisPyCertV7': false, - 'isMachineLearningPyCertV7': false, - 'isRelationalDatabaseCertV8': false, - 'isCollegeAlgebraPyCertV8': false, - 'isFoundationalCSharpCertV8': false, - 'isBanned': false, - 'joinDate': '2025-01-01T00:00:00.000Z', - 'points': 0, - 'calendar': {}, - 'completedChallenges': [], - 'completedDailyCodingChallenges': [ - { - 'id': 'challenge-1', - 'completedDate': DateTime(2025, 2, 1).millisecondsSinceEpoch, - 'languages': ['javascript'], - } - ], - 'savedChallenges': [], - 'portfolio': [], - 'yearsTopContributor': [], - 'theme': 'default', - 'profileUI': { - 'isLocked': false, - 'showAbout': true, - 'showCerts': true, - 'showDonation': true, - 'showHeatMap': true, - 'showLocation': true, - 'showName': true, - 'showPoints': true, - 'showPortfolio': true, - 'showTimeLine': true, - }, - }; - - final testUser = FccUserModel.fromJson(testUserJson); - - when(mockDailyChallengeService.fetchAllDailyChallenges()) - .thenAnswer((_) async => testChallenges); - - when(mockAuthService.userModel).thenAnswer((_) async => testUser); - - await tester.pumpWidget(createTestWidget()); - await tester.pumpAndSettle(); - - expect( - find.bySemanticsLabel('Challenge 1: Foo, completed'), - findsOneWidget, - ); - expect( - find.bySemanticsLabel('Challenge 2: Bar, not completed'), - findsOneWidget, - ); - }); - - testWidgets('should show no completion state when user is not logged in', - (tester) async { - reset(mockDailyChallengeService); - reset(mockAuthService); - - when(mockAuthService.progress) - .thenAnswer((_) => StreamController()); - - final testChallenges = [ - DailyChallengeOverview( - id: 'challenge-1', - challengeNumber: 1, - date: DateTime(2025, 2, 1), - title: 'Challenge 1', - ), - ]; - - when(mockDailyChallengeService.fetchAllDailyChallenges()) - .thenAnswer((_) async => testChallenges); - - mockAuthService.userModel = null; - - await tester.pumpWidget(createTestWidget()); - await tester.pumpAndSettle(); - - expect( - find.bySemanticsLabel('Challenge 1: Challenge 1, not completed'), - findsOneWidget, - ); - }); - - testWidgets('should handle service errors gracefully', (tester) async { - reset(mockDailyChallengeService); - reset(mockAuthService); - - when(mockAuthService.progress) - .thenAnswer((_) => StreamController()); - - when(mockDailyChallengeService.fetchAllDailyChallenges()) - .thenThrow(Exception('Network error')); - - mockAuthService.userModel = null; - - await tester.pumpWidget(createTestWidget()); - await tester.pumpAndSettle(); - - expect( - find.text('No challenges available at the moment.'), findsOneWidget); - }); - }); -} From 57b409cdb24ae6f9c12f62ad279d23beeedd17b1 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 21 Oct 2026 15:33:34 +0530 Subject: [PATCH 46/52] chore: update and cleanup ci/cd actions --- .github/workflows/crowdin-download.yml | 119 ------------------------- .github/workflows/crowdin-upload.yml | 50 ----------- .github/workflows/flutter-ci.yml | 3 - .github/workflows/flutter-deploy.yml | 3 - codemagic.yaml | 24 ++++- 5 files changed, 22 insertions(+), 177 deletions(-) delete mode 100644 .github/workflows/crowdin-download.yml delete mode 100644 .github/workflows/crowdin-upload.yml diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml deleted file mode 100644 index b37ae4f1f..000000000 --- a/.github/workflows/crowdin-download.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: i18n - Download -on: - workflow_dispatch: -# schedule: -# # runs every weekday at 12:30 PM UTC -# - cron: "30 12 * * 1-5" - -env: - GITHUB_TOKEN: ${{ secrets.CROWDIN_CAMPERBOT_PAT }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_CAMPERBOT_SERVICE_TOKEN }} - CROWDIN_API_URL: "https://freecodecamp.crowdin.com/api/v2/" - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - -jobs: - i18n-download-translations: - name: Download - runs-on: ubuntu-22.04 - strategy: - matrix: - node-version: [24.x] - fail-fast: true - - steps: - - name: Checkout Source Files - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - with: - token: ${{ secrets.CROWDIN_CAMPERBOT_PAT }} - - - name: Generate Crowdin Config - uses: freecodecamp/crowdin-action@main - env: - PLUGIN: "generate-config" - PROJECT_NAME: "mobile" - - ##### Download Chinese ##### - - name: Crowdin Download Chinese Translations - uses: crowdin/github-action@master - # options: https://github.com/crowdin/github-action/blob/master/action.yml - with: - # uploads - upload_sources: false - upload_translations: false - auto_approve_imported: false - import_eq_suggestions: false - - # downloads - download_translations: true - download_language: zh-CN - skip_untranslated_strings: false - skip_untranslated_files: false - export_only_approved: true - - push_translations: false - - # pull-request - create_pull_request: false - - # global options - config: "./crowdin-config.yml" - base_url: ${{ secrets.CROWDIN_BASE_URL_FCC }} - - # Uncomment below to debug - # dryrun_action: true - - ##### Download Spanish ##### - - name: Crowdin Download Spanish Translations - uses: crowdin/github-action@master - # options: https://github.com/crowdin/github-action/blob/master/action.yml - with: - # uploads - upload_sources: false - upload_translations: false - auto_approve_imported: false - import_eq_suggestions: false - - # downloads - download_translations: true - download_language: es-EM - skip_untranslated_strings: false - skip_untranslated_files: false - export_only_approved: true - - push_translations: false - - # pull-request - create_pull_request: false - - # global options - config: "./crowdin-config.yml" - base_url: ${{ secrets.CROWDIN_BASE_URL_FCC }} - - # Uncomment below to debug - # dryrun_action: true - - # We do not need to run tests because they are run after the PR is created. - - # Create Commit - - name: Commit Changes - uses: freecodecamp/crowdin-action@main - env: - PLUGIN: "commit-changes" - GH_USERNAME: "camperbot" - GH_EMAIL: ${{ secrets.ACTIONS_CAMPERBOT_EMAIL }} - GH_BRANCH: "i18n-sync" - GH_MESSAGE: "chore(i18n): processed translations" - - # Create PR - - name: Create PR - uses: freecodecamp/crowdin-action@main - env: - PLUGIN: "pull-request" - GH_TOKEN: ${{ secrets.CROWDIN_CAMPERBOT_PAT }} - BRANCH: "i18n-sync" - REPOSITORY: "freecodecamp/freecodecamp" - BASE: "main" - TITLE: "chore(i18n): processed translations" - BODY: "This PR was opened auto-magically by Crowdin." - LABELS: "crowdin-sync" - TEAM_REVIEWERS: "i18n" diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml deleted file mode 100644 index 4fb353120..000000000 --- a/.github/workflows/crowdin-upload.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: i18n - Upload -on: - workflow_dispatch: -# schedule: -# # runs every weekday at 11:30 AM UTC -# - cron: '30 11 * * 1-5' - -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_CAMPERBOT_SERVICE_TOKEN }} - CROWDIN_API_URL: "https://freecodecamp.crowdin.com/api/v2/" - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - -jobs: - i18n-upload-files: - name: Learn - runs-on: ubuntu-22.04 - - steps: - - name: Checkout Source Files - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - - name: Generate Crowdin Config - uses: freecodecamp/crowdin-action@main - env: - PLUGIN: "generate-config" - PROJECT_NAME: "mobile" - - - name: Crowdin Upload - uses: crowdin/github-action@master - # options: https://github.com/crowdin/github-action/blob/master/action.yml - with: - # uploads - upload_sources: true - upload_translations: false - auto_approve_imported: false - import_eq_suggestions: false - - # downloads - download_translations: false - - # pull-request - create_pull_request: false - - # global options - config: "./crowdin-config.yml" - base_url: ${{ secrets.CROWDIN_BASE_URL_FCC }} - - # Uncomment below to debug - # dryrun_action: true diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 292d42d8b..b5cd6d706 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -34,9 +34,6 @@ jobs: - name: Checkout files uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - name: Install SQLite libraries - run: sudo apt-get -y install libsqlite3-0 libsqlite3-dev - - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules diff --git a/.github/workflows/flutter-deploy.yml b/.github/workflows/flutter-deploy.yml index eaab764fe..29eaeb904 100644 --- a/.github/workflows/flutter-deploy.yml +++ b/.github/workflows/flutter-deploy.yml @@ -28,9 +28,6 @@ jobs: - name: Checkout files uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - name: Install SQLite libraries - run: sudo apt-get -y install libsqlite3-0 libsqlite3-dev - - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules diff --git a/codemagic.yaml b/codemagic.yaml index a2b251e98..5d5028f33 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -43,8 +43,18 @@ workflows: - name: Install dependencies script: flutter pub get + - name: Check generated files are up to date + script: | + dart run build_runner build + if [ -n "$(git status --porcelain -- .)" ]; then + git status --porcelain -- . + git diff -- . + echo "Generated files are out of date. Run 'dart run build_runner build' and commit the result." + exit 1 + fi + - name: Analyze code - script: flutter analyze --no-fatal-warnings + script: dart analyze --no-fatal-warnings - name: Install applesimutils script: | @@ -122,8 +132,18 @@ workflows: - name: Install dependencies script: flutter pub get + - name: Check generated files are up to date + script: | + dart run build_runner build + if [ -n "$(git status --porcelain -- .)" ]; then + git status --porcelain -- . + git diff -- . + echo "Generated files are out of date. Run 'dart run build_runner build' and commit the result." + exit 1 + fi + - name: Analyze code - script: flutter analyze --no-fatal-warnings + script: dart analyze --no-fatal-warnings - name: Install applesimutils script: | From 85a207dbce88720bedf345956e25e01d5c865b7d Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 21 Oct 2026 15:35:38 +0530 Subject: [PATCH 47/52] chore: update lint-staged configuration and add new assets --- .lintstagedrc.json | 2 +- mobile-app-new/assets/icon/icon.png | Bin 0 -> 35043 bytes mobile-app-new/pubspec.lock | 52 ++++++++++++++++++++++++++-- mobile-app-new/pubspec.yaml | 2 +- package.json | 2 +- 5 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 mobile-app-new/assets/icon/icon.png diff --git a/.lintstagedrc.json b/.lintstagedrc.json index 75b4ea770..6514a572e 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,3 +1,3 @@ { - "!(app.*)*": "flutter analyze --no-fatal-warnings" + "mobile-app/**/*.dart": "bash -c 'cd mobile-app && dart analyze --no-fatal-warnings'" } diff --git a/mobile-app-new/assets/icon/icon.png b/mobile-app-new/assets/icon/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8c584824fc49cd57d620ebba1d97bcbc699c7857 GIT binary patch literal 35043 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 DXW*`z literal 0 HcmV?d00001 diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index e410d5cc4..382a82bd5 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.14.8" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" archive: dependency: transitive description: @@ -273,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -683,6 +699,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0-beta.3" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" flutter_layout_grid: dependency: transitive description: @@ -739,6 +763,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + flutter_native_splash: + dependency: "direct dev" + description: + name: flutter_native_splash + sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc" + url: "https://pub.dev" + source: hosted + version: "2.4.6" flutter_riverpod: dependency: "direct main" description: @@ -1002,6 +1034,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: d701df3ef4761a22854c69b9661c9d8f25b264b3982985f25944247ce644761f + url: "https://pub.dev" + source: hosted + version: "4.11.0" integration_test: dependency: "direct dev" description: flutter @@ -1772,6 +1812,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" upgrader: dependency: "direct main" description: @@ -2000,10 +2048,10 @@ packages: dependency: transitive description: name: xml - sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.6.1" yaml: dependency: transitive description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index b1665e4cd..2afde6cdc 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -1,5 +1,5 @@ name: mobile_app_new -description: "A new Flutter project." +description: freecodecamp.org app. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev diff --git a/package.json b/package.json index 0bb9b62c3..695f8aee9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "develop": "npm-run-all flutter:build-runner -p develop:*", "develop:api": "cd mobile-api && npm run dev", "develop:app": "cd mobile-app && flutter run", - "flutter:build-runner": "cd mobile-app && flutter pub run build_runner build --delete-conflicting-outputs", + "flutter:build-runner": "cd mobile-app && dart run build_runner build", "install:api": "cd mobile-api && npm ci", "install:app": "cd mobile-app && flutter pub get", "postinstall": "npm-run-all -p install:*", From 4ab0526ec61d19e305fe721a26c937e602512a41 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 21 Oct 2026 15:47:45 +0530 Subject: [PATCH 48/52] chore: update storage repository to maintain state across instances --- docs/LAYER_MAP.md | 2 +- .../lib/podcasts/repositories/storage_repository.dart | 3 ++- .../lib/podcasts/repositories/storage_repository.g.dart | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/LAYER_MAP.md b/docs/LAYER_MAP.md index e7b1e1a0f..bc3f04861 100644 --- a/docs/LAYER_MAP.md +++ b/docs/LAYER_MAP.md @@ -114,7 +114,7 @@ There are two graphs because the code is split the same way: audio on one side, - **`downloads_controller` only reads.** It calls `library_controller.refresh()` after a download, because only the notifier can push new state to what the grid and tiles watch. `refresh()` assigns `AsyncData` directly, where `ref.invalidate` would flash a spinner. - **"Is this downloaded?" is answered by `playback_service.playbackUris()`** from the store row, not from the controller's cached state or the filesystem. - **`playback_service` runs off-screen and owns resume progress.** It holds raw stream subscriptions, so positions keep saving when no podcast screen is mounted. -- **Nothing here is `keepAlive`.** `player_controller` holds a `KeepAliveLink` while the handler holds an episode, which also covers a pause (exactly as long as the media notification exists). `downloads_controller` holds one while a transfer is running. Everything below them stays alive through them. A failed download is not kept, so leaving the screen resets its retry icon. +- **Only `storage_repository` is `keepAlive`**, so every write to `podcast-downloads.json` goes through one serialised queue, even after the rest of the chain has been disposed and rebuilt. `player_controller` holds a `KeepAliveLink` while the handler holds an episode, which also covers a pause (exactly as long as the media notification exists). `downloads_controller` holds one while a transfer is running. Everything below them stays alive through them. A failed download is not kept, so leaving the screen resets its retry icon. - **Both API repositories share one `Dio`** through `dioProvider`. ## News diff --git a/mobile-app-new/lib/podcasts/repositories/storage_repository.dart b/mobile-app-new/lib/podcasts/repositories/storage_repository.dart index f3cf61b5f..eacee13c3 100644 --- a/mobile-app-new/lib/podcasts/repositories/storage_repository.dart +++ b/mobile-app-new/lib/podcasts/repositories/storage_repository.dart @@ -48,7 +48,8 @@ const _legacyEpisodeKeys = { const _episodesKey = 'episodes'; const _idKey = '_id'; -@riverpod +// NOTE: One instance per process, so every write shares one queue. +@Riverpod(keepAlive: true) PodcastStorageRepository podcastStorageRepository(Ref ref) => PodcastStorageRepository(); diff --git a/mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart b/mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart index 60470e597..2cc6ab1d9 100644 --- a/mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart +++ b/mobile-app-new/lib/podcasts/repositories/storage_repository.g.dart @@ -26,7 +26,7 @@ final class PodcastStorageRepositoryProvider argument: null, retry: null, name: r'podcastStorageRepositoryProvider', - isAutoDispose: true, + isAutoDispose: false, dependencies: null, $allTransitiveDependencies: null, ); @@ -55,4 +55,4 @@ final class PodcastStorageRepositoryProvider } String _$podcastStorageRepositoryHash() => - r'bda3ac43ee0a8ebfa3993c374904e090669f8d59'; + r'159018925645b4d01ae8dd260c96133884d73571'; From 35fdff33b3218171b9c0736de39d57df1f1c7d72 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 21 Oct 2026 16:37:39 +0530 Subject: [PATCH 49/52] chore: update analysis options and dependencies for improved linting and splash screen --- mobile-app-new/analysis_options.yaml | 42 +++++++----------- .../assets/images/freecodecamp-banner.png | Bin 43511 -> 159847 bytes .../assets/images/placeholder-profile-img.png | Bin 28277 -> 22460 bytes mobile-app-new/pubspec.lock | 4 +- mobile-app-new/pubspec.yaml | 2 + 5 files changed, 19 insertions(+), 29 deletions(-) diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml index f9a460a10..92e580f82 100644 --- a/mobile-app-new/analysis_options.yaml +++ b/mobile-app-new/analysis_options.yaml @@ -1,32 +1,5 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options - analyzer: exclude: # Flutter generated files @@ -40,6 +13,21 @@ analyzer: language: strict-raw-types: true strict-casts: true + errors: + todo: ignore + +linter: + rules: + avoid_print: false + prefer_single_quotes: true + use_build_context_synchronously: false + always_use_package_imports: true + avoid_relative_lib_imports: true + directives_ordering: true + sort_pub_dependencies: true plugins: riverpod_lint: 3.1.4 + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/mobile-app-new/assets/images/freecodecamp-banner.png b/mobile-app-new/assets/images/freecodecamp-banner.png index 1883ea1d05414b3814ab231c1b4736cb1ed69b3c..21174aeb49b46280b7585db375574749b614432d 100644 GIT binary patch literal 159847 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

Ch;RhP|%75*q#7 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

Af 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 IlF|780KVYecK`qY diff --git a/mobile-app-new/assets/fonts/Lato-Regular.ttf b/mobile-app-new/assets/fonts/Lato-Regular.ttf deleted file mode 100644 index 04ea8efb1367727b081dea87e63818be0a4d02f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120196 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�r(aJ zZ33HkgG;a`qEg`DFB$$iS~G&mOkPzQ`iwI*Hi_VuTD%Q52}^*S8tJ0oD`Q)6ne@hV&no+d+meobxEgh>q^H?C1wRm~aOxy}xvhKEZC9|$udEKnE zk`0xM8dlACYViYS9Z!_s^2LJk^$YX!3MS?j&YH92_LVEYbmOd{&F@uHN|qE%FRfWv zw5oDm`O>_J3ktKQue*QUr5yWjx1RVCx@5Kwoz3ky4!vH4gh=B$Dyy?G4Kp zFRGkhGOu9nY}`_xF+Om<>UHLI3G`M)LsP03l-Z&pGgA>ljk$Fc9HUVf$-`@bW&V7( z>2yK$blz%wtSyoUE=`WcWfeGNhyHu0h7N;Bv<{I>|DVbar>WA}%GKy~e{K0NoCp>V zyPjfVLjQ=6uZgYXGuNEA8H;-uL#$))5&o~M6N&{Bv_u;|iBP;58)?9moh`veMqvm* zZ{U#RKZTQrK;V|Ykw6Sbb=gJ??pf8}ptSfmi!&8&^WHK#5tj{DqUVpw1$@QSoosM} zd(*)YdRN;g(xS_eKK9!`i56CQgXD6sLJ|p|Dsvu%SWE<6ku>eW&fW5M8t`6%Gx-1R z7(vJ#8RdrlTTESr5YG2^xxp90nL=!R8YQ~=_`lFPq7$3?uv4r8(a78_hC_aFu}bKu z-{Qb=Q3`3bHqsyfC=UB(y7|5Nn-@=uG%sbA z-xT4f`qH(U7#@Z)dKer_FrmI;qKyGQydq(DVJByr7)_$#!1)&$nZOCFwU1=Q6}eqG zZ4cyz*{F%h$*^3}Jr3nbhXt+1^?ya4@(j?^7#gG|)5KNP{1thH&DA3#*}6idWMPAW z@WZFmh$WMv1uo?t771_ZA9gE-&!x?O2%Ae!srl;drGYcq=Zt!EdyL1KUv}a%p3&#g zjWO88c$al%d)3Gz-J_!xR4{!Zh%SMUdPdoU248kWA(N*{a~>GK@S-+KR7NWlAp|AF*r z4(4yA-^ur3ZB`6^h~4z<2nng?Ugdd3RTV|m^QvdhoIFW!7wi%}@uA=EU%znIliTZ#-oI@9O}l?k)p6-7^R^vsn6ajEO8!mv zE?V<<>u0H6_F|y(Zw`)?xKTaQ_#J$>tS_ApsQOx~^y`d2kD9)7SUT&`djD6$(wV=N z{<~r6tY0hrPNrkbwZ-^8VqqV^Q>rn;SYX}zpp#co4?G`W29&@a+)6IPA_s{UD}pX! z`gT!G4u-wJ)nJZT76@)a4aW6uVJxe8TT)i|uJWv;k^i>+-wyN02)F;`nh^=)p=$7K z#H@(xjVy3bWX`BcoHcW1(X1lwg&Q|E0cTW(_L`Tc!zLFNOyMp!4iGSHM_7rO0d2iv z@|~>H-vhdf$vZ7Et>#~y2lStLu*H;qdO1QJz2e_OEeKxtMSt1H)?9ul7ft4aZulWf zsp)*+gm5+?&F0PIR1;CBaX4d`8;UTOxBtWZg*@+ZMa#V6F=ZLoeSP5)9{aeeqht~0 z@f$DwQ<-`8`i2$vteR>TFTZ=$)WU&POYHY7`r%HV1378pvW&E67eDRiVURi5o5rVJ z{`UUQ*jARz+w%2+$wgZp+Ss%C@`XIM2c7q%_CUW3=LG*x?O|m4&SB|n57zs?8kSDI zx6f`X4!T5qhkvy zCVj#!1vh_gRe9RPsP@ZE?YQx>+cy6>+x#iX(&ms$2b3UM@(k!oN+lb5EWy4!?twZnrVx12?^~` z@RwoZR|G^t-GWd#M@YsN_9&dHiGSeux=aXHf$p$62{tWFQqOMu#9Yxf@%^>rPL@bX znFrVX*8YW=Q!L1ZYB?$ob`_gCy84=O#zcx$c1SwI_ zImn#D((I5l$d0uDh!Id$NXr8A5vd}^qSvscGyEdAA?6ys|>)Qy~6UHZ;e~32DApM-$;>bjC?KtyMU58dPh9b77SX&$!AK z#x$CXdzLYwF%3?UtB^soiHlYEo%CZct$G`?%esL+_X)Sld<7eHn70RMaGmNBOBoi>FuiW%Ny-?UtVpSb@>(B znN35g9cE5LksU({i^42}{p1~6<;G(3dsP!E=9`$L%wJrw*jtQ9IjQ=2Aa+lfy{U(Wgs(((CVr` zJ=bCVcFhB^ZxLb_oV}32`zOMTo?0O(?l8eGXnR z61?~Ptg}5E(S3W>`$S7C5CYese!{)Jm`c^V$7SZl-T4Y^WU zfR$40qOdDYlJ-D@&-88os za2v~rY}lR7L^g;$yLt((mJPKYN4G0as44v{DGn`DK9u&Nvy90c=X>8vCAEG1Rns(!XeN5GhjyRep1itK@t$AY1`QMtjM_R^ts z1CM|0?PImI$KL+h;{)qbtL{C%b?fo_7A&}rfA6gdb-I7Jul)-@GTo0Ww4$T&50?vC zU+G-6s`HiB)>pooMNJrEEzSlOWf8z@+OaH)MZ##CbqSv{26~>H8H=GxXwegE zvK$iew3ojjm%xpf)SBg=ToyAL<1*|{wTc4u4Exuxf+0(e5~Mk!Wa&0>LK&-9IYqg2 zz26YeBeNopTrgbu*JY*knC$RODL{<>0$ae={pHzWl%v&DY+}Q4!C-{j-U0@Pf3e-C z>c&yBg=00yI~NwCTxv%{oQLHL&oQ@fVlh?Kjc^O)tI52KvqCF|j}YL}=j1)y-;WhV z>kL}U+dpP#QSpz%78U1jsG60S5?i1(#e08PJnu()wjA0~Rdw&t+gpB6l>gn!NB@5J zq(xg7FWtI$(jNQgu!uPQ%0$6WdY`dK*86d0LfHHCb6W5JnkzlX zCkkVR-z)ynibZmni!=?G*`e=jet=I)i)lo@wUe|q`miQ`K&dSGov;^O;u6Y zg@74xXylcI7Kh;@__E=IEes-Up`*$@)=&5WTgH(KPbyDANR~#Mp-t6Dw@#fyBb(g}?)~>8j&HggP0=ogE27C6J@DVu?ZCM>P_nzEqZ1>(Vp{~+r5InTLg0i4_e|i_ z$rLk1E1uM}L`1MrdT<5sM1vSJbe11Z*|ds;K_8XJTKx>XoQ0A1B=mVGvKE}s*NyU$ z!|L(5H@hHswkQvtHuG=Tx*+}jxJlj_GrW`H?oVIv`G~K~xc;`{EH94io9fLfzU}%M zl*-wN@0;ItT*O}T^r7<#;>a3XfQHC6PCG^%5omtfx-ru_=b1NVI!c45K938#E7Q0$ z;tS@V9lz0Db({mld*XR2g3fJaA0AU#-aO5Stx^kkS4g<_v#o{nMaOCBP^}}Kv zTVb7la_7Y2>Z#MNFPS*8PTTamq^a?o86%QW<_*lnaPywgQkCe!yxx`M!j0`n3En58Ru1aY z132!`tUbn1D_4qs%(i}nv@rbyqQFq}T!xD(@{k0JpcfD$1d0c0Jv`|A6P#a%PRFr! zLa{S2Pd^`b3n2^OIa}r{4WR6bG88e3bm`2{(AjT-_0?X{FVtPDNEs#(7D+cm_5}(@6AIPB<^D%?`9<@F4K)9P&PO^mp+N zKM3^0aH+HJ5_@rQTW@zvx2enlqE5+Jv;(CAcud26xhq`(1)bP zezzjFq{N*I^C(EI&p&p40$|5c|>zVtP_?{f3KdtyDyqY-#lvC!tP ziW!^4OEOJkpUrLmp6wwF2(JBnwSD%9mwsz|s5$Vx|wk%Hc|wFg7kJHFCWDk9nWT+jw~KBuA_> z&XqiK?z{;T7v;pnjz!bJzB>EAn@b$OxAKMNU|T=q2Aq+K#DV-^nsuIs8J{t3!>aI% z9ly`poVO{Manh_n#xY}gm(Rw5cg-cn`(gP`xz>E&ziPJ70?5I3ljJa$=a6Kk!CQ7! zSs+&Ennd)>waaYgRfTDzENwd_OO-7RhtZ>KSCQv6i^F{M+66!J>Y{_}P$#yXs*~{Y zU8NvbNxo7It}NeGEAW$w2c6kD@Nh(;<7p$){9;vH=7jNSV{j)-tSf?d!Yn^CAC_na zoC$`BO*wWWeJnN_aAywgFNQ(3<9ixuUK|(^F~*t)uxFok#V2?RA4H%j!=CVS>jQRr z{!wWR`yjM^%pIZ81E=+cSI|Id&>he%IgA!;A6!(7k-%=Sz~sFw^sz0(ll3voAlbq| zljX&;${Usk3}<=pYsZyeBLoWlH9o?qY}C&+6~|~Lsc;_M^{ICJNwrs)#k?OU%aa5f znw#Xw+Bt2;q!p`I=Z>9PRGgE#^6|wprw-hgnp0dfb?m^s_U_EQXjiP=d=qE(jEx*; z=4F--Y`*kgsj2p8bH8k7uwLhwzpolIXZEa_({i)3a62FDl~uorwP5{*xPh6?v^gRs zPtN6z7?vrb z%x(;37tQPa}e?Gunt+p=>mZ(DuxwSoiKaht7sE$qHty-c+=7< ztOfB7A$UDU)h=`Q)R~JPUuh}X$^(mMOf&b4u4|W%m1pLeX^{hIsj220k!jRF8{@%D z<7dWi?HBAXU?f-`nky|olZx8WITw^O?1hPuAEq@N46?210hP&rXIXbHjhM3htZYli ziW_!+uB_~HyKh+0v8Bvb6a3c=_!oYKX}NjueaCv^UC_=o%8W;O#}(sh8M7GJYp78u zf@>^-I3+^6oPrBYiNk-vUMBv+rQ;0#Vik7SJ63_mBdC)jYqElh!r|1WRgFAG4^P9` z!)%llRy=~iDWCH1n@1k<5aAe^f_?s3v&SKxGbw2j|7In9aYS%u@H!z~I;2a8W^-~n zJNR!bVpI>l7xf*-QOR(wvDnyPY%?A-e^oW%e*ZnbJGXAG!du8o!&^cRu0xZnQ&!lslesL88$eEA-K#^at+^N`6E^tux+_8 z+)i}Lm(6-DzsmR6xe|7Ov-aIzkw-9&bUr!(8$w$1!2#EZSoYC5aya6L6(q$f2$f}7 zC=*0?L_4r)>QgdxREAG=|!6Z^qgG;I+UKQ;_y=61OqI4Nk9%;4dPjf%lE9Bal# zQ0CEY<%v1E%jwnzo8WZ-$~zR`HPUSnIb!ugXMxEj^=WTT8Q}mKnUn3A!kg}?Ty*#P zyu9^yFS>5WjdTA#eeQxOQ*g%m_&oldn{I21P9OWgpWkw&C$&0?3U9i5==FQn=Y4tP z>-Mdmnqx%ey0%Sud7IkSt#7Z*;{_HAYsP|4%u50Cs zt?Slz@Wqbx3vVeM2M^%F1snO|#s#19yX+s%b~pc3XS-w0U`ytlVYGAIM;_sR)g#+s zJ9rMD`I^oFM4pK_2o9&XU2))_`P#4}{%p8`akSI?kl!FY+4gMsq|mT62Hd_Temd$QF0 zYSo0LOR8{Pp0_wZZ^ks7AGu-j)awWP#%Sp?wo+sD|cx0}cdnm^gmx80% z$ZP(#99=HhMI1#HC@cSP_2;wKk}Imh&CBl4+J}Y(IBz>LHil_VoF!&SC2BM(F;@SR zRG{0)hPM4UBQ;paiZfHv%~b1o0`8OH`R%&2Oy?+P7Zv0ba(czLGw6;Lh}a+&gb=`HYEau~D&6%a{D@p?fawzA52r z?fAXJ-g5s>9$K;G=0(x&oY^@q{9|!qM)K5@gbDE_o{X1XN|`uiVoGG>lH2cE^^I3r zZjYakkUT9JgX${#^^R%K-3j3IjjFMknVC76Ia4NM(F5zGX=9T;^v!E*OI<2T9TOjC zcVH|JU2@1rxK13$OfG?;r<6=s?ug7nuJ{W+qT1 z&M>N0gnbqng_um-zvqA#hsWUH@Ch8R3zLqQadT4?0)246o+>izjB~E3TiGGl2TcsK zS$N$DsVOt&LPWk*mn)wD!9yPfV=D6Q+ z9Jh%5j=Syg7ju-UIqA6DJJuz?CuN&6HzNgIB^MfCPuS$Rkc9qIeZ9_G4*0+5&zd~+ zHpH$umi98S-RfP%(O9Fc*`vP;PaoE|!U>R)j`$em1Fk6WefZ9>BPB2-Q_;A_f$sut z&@2!AEkjqpqcAXU>>cIHO`_ z#nc=`dukjTI$;GTDcg;_bm$fg<i}VciiW>rgccIB zte?@gARsg|?T12Md>{WdxHN|dcsQ?d|0vo!UCS6y4;?FxrC!4}%CX`yc%gH}vx^*Q z-|ZiW>HjW{694$P^CtfHP4Ku5JleryqVcsVg?rHgYxRk|VOJqf27ze;6Evqf{f(O{ zWTtSichE1N8F~{A8~#2e>=Uk4RZR=M0okIqX|8RfT`y;?Git+L3eRI>HyMp;D_R%v ztxKP?&AhuID3*J(OF3@)Yo)3YzF&6Yw4LS-Le(2b~)B)7q0qF zJm0Cx4bK9@Gaj!*k!2HParlSUl|2_01Yk?iNd@j?wn&ur7Kk&JUizbaP$#5)9Ncng*T3@L#;FxINubKTS<8al4`Q_85 z@=Ut&Rr6OB%$-^`tt>Nxx216(k)r<8g)g6#hO4>J{X_R)hM;ud*X4|cm18fQt;YzK zoBPE1!2V=m=rd4`bl5e&E>28}F(pu3fPpBYnXQn@TFS zug}k4zy0#6$rZ)_+No6ahPhU|s zDQ|;+#R~t;bA$h$do%yNA#d-5qQ$wniwiTy&Z*AJ!vz+>znO(hFG4FC>>fB^uf>=k z+vu!vWu}iwcH^{$PX-V$taSz+&TBP`xfo0IMs9<{j{ zVede@ptDj_?uteUf;Tzy)1ZyS5+Ae-vCh0o+HM`1pp_`B1e-fk&hE`wJTb}^l{jJS zmWKFd_kzzpxIBB=1eZNJd16}WP4lNjMO^npe_X=)=s1^S+s;4#>Gr+1&T=QLh;=*7 z@nyFxR9|q$yx;x{`*S#jsMt)Z8kdodfQfuY&8HV<6zdk|DBgx0IumhaGTNR!$%)pM zmjz+XnS{=oXBMFSvFwUJMI1OyaNwF`xW{^CGPsP1lW{D>RQ$ zl@e5V7S`8O(6I!@XtaQt?5wF-?v%9HZ3T0)rezI0;YR9R zd9(2x7f79&h1S?@eAoVd#QpGT{Y%xDBpz0e`-#H3L_;;k>#pftG0dr`0f%!MYAE2n zwxOo5Yaccv8xK5pb25zy{$d@~8nfcI3{H!2Fn!TAepgjF^a<2!g!dPLUJ=*=35p0s zqZe4S1d(Qqf--!+urWzlxC;W;Az=2F6`5U(n^=ppy3MzI5BlC8_}Q)ILj(EuK6vlI zAI;^r4y?C-_5JrdUg)@Vse@+{*o;nNwf*6Ue?VM(iZQDSmlY%>!V^%H=}CYxIS|@G zZDI-Vz>brK-N=d{Rg=q=*d~UViDQD90lyyTciNqa;|7+_E*O`eVtywr>C&I29}OXR1uu^)MW#0EXBVh#b8DDf5oIH zeCf~s7%0O7gL@$B55RJ=kynLghz1KeO!CCJoOW7zPI0a-ih^h}8>cmBjuvfdALIjO zyWead_>%tvGdU{W;fRYi#}2%i=yJqI*}iWty7ZGvFWL()e>XKJF)=6Aw%E30Ms{Kj zq7Tf$fq~~*OBiC7;1Ww|h@^rxj=1g3( z!^|0V06T&eKiuMoz;r80omDi;({OV&$+lgW&Hlqdrse- zvns=BOHPUz8~^RcZCjokSZBY*?MO+!zT}oiwpX}Zx40wCZ*&%1%Cv93v>#OwsM8M| zXR&^JfH$w5 zMxZR*A$&Cb%8zhb8EQ#G_@VDD)zMfm$PQ-)G$F3N$8?*st9{;s)Bw|XaiGim^57X5fa|K@M2vO8 zL+Vy!Ds2X~PT?g~H(-oO6M?N$On?A}eqw^P?1oKcJXAzyS0tHRQ*n}iT9PXxtzz!# zlvvk<{OZCnakwQOcIxLiv+G&sNAMwMkS#0T`wz}Bww=xowBEFS#2tvNwZHW)XBJ_^vc$-%oL}UH z%_+IOnmIRx7dV?IaB1@xT-NOI;PU1%xU|{XJSLa_%EeXB)&gRRwn@&f z!**YLywW1`Hv{9XyGOk*fhmFPWOUqlKee+ung+dd8)E z`4yKZag4|L(Jx}7e&UM7wtkjp@LlA0pYzuU>8`B8;Y@L{Fk!R?lq&*(F$#rw4-ZVn zhQm<;lOaOZ;2d;q*w#gNe{q5BzK+=R=;o-1sEFq1^w^I7JmYjlI&Y1@T?e;1BVQg) zKaunk6VR=O(TCDl8`T1gF)$iwq>n-%2HDzWhd^Y}$61DHB8*u^oRCmp zMb;K1JI#VUU%Pwd`t>XCe#r4(cl*lb&oBGDALZ$HW}2rWlc4X=k)gZKtY$i54$ZsI zMy|P;r;;blN*gK4wva=WeN>1j#W^y%mCWLP@b~=7)z8^UYbTW;CVKK#3 zS$KiH8B8C<&2Erk)zC0l3I4H0cq7)M!()bbW)>{U;k=OZ#+*e3nHz#J!_Hl~3+Id* zH)mmPP8FA+t8!*|y)%L_!{eM$w)M`JF@HM4cv{{Y6^N(BtVw>UO{kCm|3%BP8iobS zI)|cVPi9XsZyPhcVrq6(S>f18Q_L-?Gb(elt4a%ZK)jOwC{ahc<0OrCLl;RHOe zUD%A3XqSI-z60BtWNf`kysR|ye=I7N9LT6M6gDgLWFC8-k+5G&$jLIF|Im|>viYf` zsS};={5Z~iE_v*a-ElzZ?8^~;^MK=-QTmuKqQ2s-{zQRb?rZ2{%wc`ZgJWVM6DOvz zkMTO5K_`<{ws9dknWc~v8b$qY4Kp!&GZrLcUq=5LJ-7@QKbQ_o7+eYT=s8j8**J~O zVNBL@j-ICr^M!>%FHQ$^z?(!Y$10M;sMPbK!GFZ_lEFj3rNV0ATH!k3Hc@O4HVT`B z&7#;MY!$W%+l3v%-NHR8L#MDy*e&c4_6i>s_Ni>%zBG7w-z+ zQ_2Uz4}~8iXWVWiWDUAhgD%ye%U}(<4A!7aHRv)}gD!(L=rUM?E`v4bGFXEy)u2l? z=u!>3a1;1HBAqqpQVqHg%fe^Ypvzzlx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0pvzzl zx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0pvzzlx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0 zpvzzlx(wE!%U}(<4A!8_U=6wq)}YH^4Y~~0pvzzlx(wE!%U}(<4A!8_U=6wq)}RY# zMiW_sE`v4bGFXEygEiO4%^z$MeR)r-8M4u2;_6lyig7r<6uL`}N$U=RL~5 zQ`jZ!7WN2xg%1n+MDbZY|5W&jQeIO&uM6K$&Tk6e7QQ2VPrrU3{80E2(i0$!mB47+ z0+Ilk@YyYl$2$qoZDOK+oh+mr5+H|v0G6UfBp7AjIswwy44e;Jfw_ZztyexipfNJy_ZX~q z585enX1#k1*1HF-mCvkq585oBS??aSL?Y|mgO*5Sy?f9SiL7^z!Fu2ZRzPIECt}Q!2aHBaBJ4Du-NN|6Dm*8k z1`|>Hd`{Fm8H300JV7`~Z)QWPiLiCdAxF>Cg|kq$M97f8777;)eg)4N3;1XAi%!foQB zLFiM;J+Qlp#tz}V%Arxe?ox_h?=&f;S*2Zs8tL?-X_kyM;Z%Ug5*S z1LEbN@R0DZ@QCoJ@R;zp@PzQB@Rabh@JZoQD*stMpA()JUJ&*RFABe_H@_$RzVHXa zr&a!E^v<&?;d6R^UihNk`Ki)hQp#(R%LI%tou^b1eGVY_v^2$3xGvp@GCiy)#)@Jop$=yeL&REYI(Nt9eFKkvmEy7k|o3LHjA>1wOQ_fH8`4v6?TvT2a{zCYM@_bYH zmP-4!p5GC^tCWxQ{4w%@qZi|c$>5jh7ABy~lM(g!0x(=QmJd`#G%#Yh9|%7L=1S{wrFFT|x?E{puCy*!T9+%W%azvU z!oIzVuc&po(mL!DfY#+o>vE-axzf5^oPqocq*Lp1A@i4k)Vf^Qd?K|j7b7$xwJsMU zG$OSw7b7$xwJsOd{tS>>mkT@3XKG!pL9NR*sCBup_RN!7my0pa3qWdJE^I!LT9*qu z&u41gH1O*O#-lHr2JVQ7O34^}7S9uelQ6cQ27dYGtie*?64084u_&=xxLR+n5w2BA zjh@%(`9?k8EWB6PtlV0Jt->~8yRbvJTXo6?i6+jyM;Z%Ug5*SdoSvT-{!}S1>G?I4>UH6J!ViQW0_PZxf!_dKLbov9n1_@E zXz3h`X!tw{Qk?@yo&n|vrwj9ig~H;&b@DjMeo0PIg`E&}qgx$g(VXyFE zVV_FKT9|{m6_K?t2XiYTYhe!NRz%jq9L%kVtc5w4TM=0cb1=6eKC3)g3v)1^BK}k< ztc5wSHk8I|D&gzGH&o^~g>OmDZ|nIT;k!zCPj7x8{80E2e$AJqMI1itPrfWI?stZ5 z%$KFjm!-{@rOlV6MK7l}X=(FiY4c@i^JQuCWoh$eY4c@i^JQuCWoh$eY4c@i^JQuC zWoh$eY4c@i^JQuCWoh$eY4c@i^JQuCWoh$eY4c@i^JQuCWoh$eY4c@i^JQuCWoh$e zY4c@i^JQrZ!0%3@0Nh0ZJ;Fq#Ocu_9rWIhO{}ONs^uGZ5MywXD6|NID>-`pCtFTSj zF6T?UF35C*xLTN&w zG@($MP$*3(lqM8P6AGmXh0=sVX+ohip-`GoC`~AoCKO5&3Z)5!(u6{3LZLLFP?}IE zO(>Km6iO2cr3r=7ghFXTp){dTnouZBD3m4?!4|!Yvwi53ERq+fNM4{Kd4Y<+-HS*s zfzB7nCsQP!Op$ytMe@lM$tP1JpG=W_GDXHCdVinrQQ>1kdhLqj8!3jI{fNJ3UtbIh zNo0>-tRBDE$iQs67(G5Q8*QyvJ$^CD$>-_9S%W2b&ewCHaM7R>DN6=t1D6V`h0Cxr zsMuJo+|~%!D!oR}>-2o1p4Si7;mw=%yg}vJICu|IHVtkE)+*(e!QFV?rsoFXy?Vb< z&rL#(D2h?1M2;wmQKv+XD2h?1M2;wmAysnJDeMw<3wwmU!iR+i#N9#RA>m=+5#dqc zG2wA>bV7JicuII$_@wYDmHDim&k4^9F9`dE7lq%|o8J?DU-$zdM=8bdfKleps)Wz! z{pW=*>Ybk|{UxQmCds@mQpoFp;C060{d0M>Qp&NaU!-E8l__RPMtweG6Xs7QCQXg=hK}yr9cx z`WC#<$zwqJ7Q6<13tnjEi$MAoywFTO)3@NoxO6L!z6Gy*3trgLui%-!1ury{NZ*3j zpl`t|-+~wV$(-q1@S;!Wuk09v1 zx8Q}Ak_-A4yas&>UW2{`uY3z$gT4hXd_Me@z6GyA-+~vOAEwZ^;Dv;V^euQHVIqAC zUPzco-+~u9OzQM4cwrfc^euQ{8Hn^Pcwrfc^euSdrzO(2;Dw)-NZ*1Nep(`Z3tsqX ziS#XaF_s|Gx8TKCf=J(j*Pw5~ixCB%>09t(L_wr)!HW?Ek-i15LEnPcpl`uz(6`_< z=v(kYH&}l97QE2m7lHIGcrofA(zoD+y<$3j3to&mC>i<|yas&>UW2{`FSLt#Lf?Yd zpl`toO=Aju3tsrOi1aOZ4f+%p@9d0)WA|{U@0`P9nT5yTb4ot`JAYC zCPD8?p>KSD*5JdyMJQn@^onnCZK4!2WHBRxqVONUbP^ zRuFdx8yv6s5Pd%r#C+kekf$Em7>-TVE;X5LuF|DzXNhMRHoTb8QMFaIU6cN zjr1Al?FXEYF^)ijs%g~CSz%yqUq6#xLMB|2KV9pjaW%6hh6mpYn4*3@^8~u4MLw% z?!io;9BqequX1SAue+4u*E>y0X%;Un!d79MuwB?8+%4qHs~mMrD79J5E6&@2FR~b$SPYO>7PYa(EKBe-X)$=*wdEo_Nzwn~)yL$6` z!tV=zAbeWoe?}BJk1L1%u&$pMzNmLNn=6MFG5s~+>%uoAt2c#jNjKis^E<+KmGYk6 z{6P4j@FV^DG3d^RUR?xIf96AfehkbJP8a423x!Jte~Dk03af={h3kat2Y-)WH((|> zAGJ^1rkopujlw439_7|4>=JehdxX8hhlR(L^9kWe;VI#1;giC%!gIp&!VAKF;YH!I z%I!HlKQH{LQeM*YYbw?2!uNz92tNc?KqkKca*ewJt%=Ar?h3RcBKwgF^&=JPM=I2V zRHz53fc@b+T;r~QP7=AsT>+gWa*ewJI!UBeuaH%*kX5gcRj-g$uaH%*kX5gcRj-g$ zuaH%*kX5gcRj+{kVQ#eN6|(0Qus=+pRj-tfx>7#sO8KZOQTs03~KI%&Os4HQynNA;dCFXbhl|Jf9NRJfhqppPXi1blcLV85{s4F2oB7M}A27T0( z27T0(27T0(nEkO-^ifwr4n+E>E75|OKYi4dnEjC=ebkkh_Yvu%u7rK&uk=w@!aft} zqh16V_5)o)w{Qbwy9n0r55QVI*DKv8yhpf0xJ&34z9D>5__pvJ;YYy5pmGT462>CW z#i%c)crm7044E^fQqNU--Z1z&QZ~Woxfo^O^Jb;j>noqoFMLDzrtodyJHn5E)uLN1 zy49G={}w5vTP?cPqFXJx)tI64SJJH(-D=UT7Ts#pJHH~`YSbx_bgLm}BI#B`&P39^ z9(~^;AR>2wh?oGz58e;-2uPZwTKMzNN3;*7G~Uca`!Ho|mfz zm#YSsgS&pDum+c_2A8V_m!telXALfgt`b>;%b}%2*5Gn*L}U#v2S-HK;Bs)(Z>)r6 zcow)+SS?&DTqis(JRv+OJS99Wd{TH;cusg;ctO}NyeNE5_`L8X;A(NVTHLJ`cdNzS zYH_z(+^rUOtHs@FakpCBtrmBy#ocOgw_4n-7I&+~-D+{SMpV{_${JBwBPwe|1viXC zo@+#9ji{^=l<3{K_aS{5a8!=WP z9uyuD9u^)E9u*!FJ|lcf-@XgnfRW@AK<2qYd2Ya*h$)kWOVDyRKtGAq!nMM6!e+hG zB5W153EPDo!rj8-djEv*r0|sRwD3vcS>ZY1dEo_Nzwn~)Y325uo}U-KBqT>0Fv}o* zjCVGIqo;u$;akFYft%FgHmSvJ0!Pmxg)MFq`eGtm+$Qv}M7Fq1=wXR$ahuS?64~N5 zp@${1#ce_lOJs}NgdUd27Pkp~E0Ha(R&uVDoNFcLTFDt}wkT~aO7%LBa;}w}Yf&mb zQ_i)RA@Uu{xfUJ+BIR5Qj{%W#u7$^dNIBP{{6xyR7Ud^W&b25%k#ep@`H7TsEy_=% zoNG~jBIR5QF8EA2*MbWo|T$nokl)&8u`>|T$nokl+OsH0_=$urip9%G)BKt>DKqo$Zn zk8VBci_i4y)`MRnV@>P9FOjjP^_bBT87*9o5dohWYg!L!90D#CGS;*n(jYR{v>xLh zQe>=YJw`i3#+ufn_DPYkruC?OB4bVKQTs&3n%1NCiHtR^hdhalHLZs{iHtR^hdhal zHLX{4U%jIH>J{BrujsydMfcS!y02c*ef5g&tB3qqLdKfbL;gg@n$|=9M8=xdL;gg@ zn$|=9M8=xdL;gg@n$|8)}t<162_X=qb`VyHLXWo@R_lu^%#Ru0*p1Shwia1 z7;9ROaTik*|xz-GatsYZG)92GM;T4>V?R7wr!{vV!M#>Y}-&T zM8>mi1MhrhJli&`FEHmpXHrRhYGoGzMoHmHl z265UTP8-B&gE(yvrw!t?L7X;-(*|+cAWj>^X@fXz5T^~|v_YIUh|>mf+8|CF#A$;# zZ4jpo&_I@loHmHl265UTP8-B&gE(!#O3KT4hnzNu(*|+cAWj>^X@fXz5T^~|bUQeG z+1L(FUj;H!XuH;rwu4_jGg4?f_$4w@Xgl~NawTy)_5SRf zj$W9^n2qh=n#h=q?ckdEFlJ*rxF#}YV>`GeGG=2txF#}YV>`I!Gh;TkYyD`u){nMp z{b)P5W}b}M*bc5)TE=W_2iHW#Y-|VDd}hpsPqxS>TjY~1^2rwYWQ%;VMLyXgpKOs& zw#X-2 zTjY~1^2rwYWQ%;VMLyXgpKOs&w#X-2`j4$R}ImlP&Ve z7Wrh0e6mG8*&?58kx#bBCtKu`E%M10`DBZHvPC}GBA;xLPqxS>TjY~1^2rwYWQ%;V zMLyXgpKOs&w#X-2`j4$R}ImlP$UjHCPS*Z=!Ir(2LsO zIsviVm~yY^DBD(7|%OIYlmp53~B&{8y zwL`Rapa%I4Y3)D_5=mPopePqbyIOEKj2>PopePBTCC0Xk!~?c^YMT8fAGJ zWqBH9c^YMT8fAGJWqBH9c^YMT8fAGJWqBH9c^YMT8fAGJWqBH9c^YMT8fAHQNvgXf z)m@V6E=iRuI;B|Gp;UKCs=FlBU6Sf9Np+W`x=T{sC8_R`RCh_LyCl_JlIku=)el_Hz4pBJIM`_1E_p818)!zN!i_|&x@T1E_p818)!zMT?|!v+zuLQB?cJ~T?pJ&FtG)Zx-u-ItezkYM z+Ph!v-LLlUS9@<#yJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9o zXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dq zQoCqUyJ%9oXi~dqQoCqUyJ%9oXi~dq2B*u6X7EU46l=3Qgw660HbYlmMG8HH&CpUJ zJ%r8BRX)>0*bLsGTk;S#Ljw2VnI6Js@Q&FIxMm7Hgw2>K6B)(YEDvF`JcP}V22c?g>!CH_heVKX$76zL&shGr7!A#8?b66qmqhGr7!A#9e1uvs3$W_bvk zAw!mm9>Qkm36UPcW_bvk6W7Rj(h zGHj6yTO`94$*@H-Y>^CGB*PZTuthR#kqlcT!xqV~MKWxW3|l0_7Rj(hGHj6yTO`94 z$*@H-Y>^CGB*PZTuthR#kqlcT!xqV~MKWxW3|l0_7Rj(hGHj6yTO`94$*@H-Y>^CG zB*PZTuthR#kqlcT!xqV~MKWxW3|l0_7Rj(hGHj6yTO`94$*@&2Y?TaKCBs(9uvIc_ zl?+=Y!&b?#RWfXq3|l3`R>`ncGHjI$TP4F*$*@&2Y?TaKCBs(9uvIc_l?+=Y!&b?# zRWfXq3|l3`R>`ncGHjI$TP4F*$*@&2Y?TaKCBs(9uvIc_l?+=Y!&b?#RWfXq3|l3` zR>`ncGHjI$TP4F*$*@&2Y?TaKCBs(9uvIc_l?>Y?!#2sVO)_kg4BI5bHp#F}GHjC! z+a$v_$*@f_Y?BPzB*QkzuuU>-lMLG=!#2sVO)_kg4BI5bHp#F}GHjC!+a$v_$*@f_ zY?BPzB*QkzuuU>-lMLG=!#2sVO)_kg4BI5bHp#F}GHjC!+a$v_$*@f_Y?BPzB*Qkz zuuU>-lMLG=!#2sVO)_kg4BI5bHp#GEGHjO&+a<$x$*^5AY?ln%CBt^fuw622mkiq_ z!*cSh_%4n*#Z*n=GIH1Sy_#k3 zh0K{7XW4rpPa*Kv+TW)8rQdqDXu%kb*H%Q6xW^N zx>HUUxb76!o#MJvTz87=PI28S zt~rQdqDXu%kb*H%Q6xUtix=UPliR&(L-6gKO#C4ar?h@Bs z;<`&*cZusRaor`ZyTo;uxb70yUE;b+Tz84c{*InYeOI&w}>n?HK zC9b=~b(gsA64zbgx=UPliR&(L-6gKO#C4ar?h@Bs;<{U0cZ=(8aosJhyTx_4xb7C$ z*kuUa=oZ)A;u^7m(By7$-7T)W#dWv1?iSbG;<{U0cZ=(8aosJhyTx_4xb7C$-Qv1i zTz8A>ZgJf$uDiu`x47;W*WKc}TU>XG>uzz~Ev~!8b+@?g7T4Y4x<_31i0dA4-6O7h z#C4Cj?h)5L;<`s%_lWBraor=Xd&G5*xb6|xJ>t4YT=$6U9&z0xu6x9FkGSp;*FEC8 zM_l)a>mG64Bd&YIb&t625!XH9x<_31i0dA4-6O7h#C4Cj?h)5L;<`s%_loOYaosDf zd&PCHxb79#z2dr8T=$CWUUA(ku6xCGuek0N*S+GpS6ugs>t1o)E3SLRb+5SY71zDu zx>sEHitAo+-7Btp#dWW^?iJU);<{H{_loOYaosDfd&PCHxb79#z2dr8T=$CWN5Jn3 z#v|bFB_Jbv9|3nvVPx+kC>xQHy^o-5L`L>Lg0c}A+4~5}Mr35~BcR9}7}@&>@_)hD zr~LOR|9#4TpYq?Q{P!vUeae5I^53WY_bLB<%735o->3ZdDgS-Sf1mPyRQW%u{2x{R zk1GF1mH(s4|54@tsPcbQ`9G@sA65R3D*s27|D($PQRV-r@_$VEKc@U2Q~r-B|HqX7 zW6J+A<^P!Se@yv5ru-jM{*NjD$CUqL%KtIt|CsXcQ*2%zsMNy8!PvY$L@5v%o7acl zc{7l)d3~VEcNm+8Q$K`^t?I)#=M0dsd3_k|@R_lBeTvQN!)S-kjLqwVSLXrX66ML* zygqn!h>XqaQ*2(JV)Odo=V3Zy^ZMZJ;WK0N`VhJL0FbeHeHa(*12Q(R4=dDuAY=3T zu&%=LGd8aeBMKs8^ZMYk#Qqt01c{8z>%&NczcMzj556M4!`Qq&NR_-WHm?s-B{DXz z4^kyEHm?s-B{DXz4`V7KWApkjrXn&nuMcA?B4hLV;5&K%cu;sqcv#5TygvAjh>Xqa zgYRfFkg<7v@Es8uo7V^55s|TZeTa5u`5Bwn2M-dFv3Y&)AQAh87ln+?>qE3Nk+FGw zh;}A2Hm}ckMid#F*9XrMzh!J*AEKT44rBBB;9p|OYr@xsjLqvq+$@o?d3}hRMr%aI=Jg@knZGhNZ@+BaerVmRc;?7$zii!p*}DDEy%&(ek=uS~9pB-| zZNF^Ye%ZSHvUU4q>-Njm?U$|FFI%@?wr;;{-G14+{jzoYQM1g4Be(siSt3Vn`%$w* zj@%C@MHX3bb&( zj*z?@5|u-ua!6FLBC2%qa!6DTiOL~SIV38FMCFjE91@j7qH;)74vWfRQ8_GL4vWfR zQ8_FshehSEcsVR8hehSEs2moR!=iFnR1S;EVNp3ODn~@+h^QP9l_R2ZL{yH5$`MgH zA}U8j<%p;p5tSpNazs>)h{_RBIU*`YMCGWc92J$LqH%t16?BH+>U`Rk#TM(ME8W~o)Fy=qI*JgPl)abw9v2M zP0~Ffx+g^Ugy^0S-4mjFLUd1v?g`O7A-X3-_k`%45Z#lads1{yitb6#Jt?{;MfarW zo)q1aqI*(wPm1nI(LE`;Cq?(9=$;halcIZ4bWe)zNzpwex~D|DqI+6&PmAtp(LF7?r$zU) z=$;nc)1rG?bWe-!Y0*6`x~E0=wCJ7|-P59bT69l~?rG6|3UvF8r$Cp;_Wl%d{w#VptEAGyUyR+i%thhTX z?#_z4v*PZoxH~KE&WgLU;_j@tJ1g$aio3Jo?yR^wEAGyUyR+i%oamkt-E*RQPIS+S z?m5vtC%WfE_nhdS6Ww#7droxEiS9YkJtw;7ME9KNo)g`3qI*ts&x`JP(LFD^=SBCt z=$;qd^P+oRbkB?KdC@&Dy5~jryy%`6-SeV*UUbik?s?HYFS_SN_k!qN5Zw!+dqH$B zi0%c^y&$?5ME8Q|UJ%_2qI*GfFNp31(Y+wL7ex1h=w1-r3!-~Lbo)iOUv&FLw_kMo zMYmsc`$e~3bo)iOUv&FLw_kMoMYmsc`$e~3bo)iOUv&FLw_kKGita_xy(qdDMfalU zUKHJnqI*$vFN*F((Y+|T7e)7?=w1}v|F6BXfs*UI&-xu%UzTn8BTA$AD-$_&>{wdK zj^o%$abjC>Y_ZrOcI^lasR>36 zL5zi0ZkGjemthd52$P+ey)$IP4BW$V4h_%mxigkz$7$7WdnB{cnRo8qx%a;J|MC9s zec$*0KF?9td(`zFb-hPj?@`x#)b$E&QL#}NbuNj<$6W6**L%$M9&^3NT<nFFE49BpKO5#Cu8gQWEiAQoWQ! zyyG7KJnr$&;~xJ!?(xs#9{)V<@z3KP|2*#T&*L8dJnr$&DSOwHy=%%|J!S8jvUg3{ zyQb`2Q}(VYd)Jh`Ys%g=W$&7@cTL%=r|ex*_Ub8n*Oa|$%HB0)?|Mb`mV;MRFXd$} zuTVeY71jHMN-)P>k)MpJ{HXW*IlT$Lq8cglK7P!{7hD#-r!0SaMcSi0=>0$C?Wg_g zU-BMM#a@wRDd)VW?y})ynl2-kEtd=awd1nuGS>cSTXx!(owjACrI}OO&$DdWmYud` zr)}A3TXx!(owjACZP{sCcG{Mmwq>Vn*=bvL#@3m!b!Kdx8Cz$@)|s(&W^A1qTW7}B znXz?dY@HcfXU5i-2`PkQvBvOy^6(TDEQhwjmb?$L+t(TDEQhwjmb?$L+t z(TDEQhwjmb?$L+t(TDEQhsp_gQrDzMAG${$x-J=iP zqYvGq58a~=-J=f`TR4Uuedr#2=pKFO9)0K@edr#2=pKFO9)0K@edr#2=pOy7{OV{h zYyX**|GcbiR@=?Wb0}GDH!Gi^WVPL_yoHk0cC+#gN>!)h{RIQ(? z^;5Nes@6}{`l(t!RqLl}{nV_Vn)M?@J>6l=`l(qz!r<{9^i#8bYSvH9`l(qzHS4El z{nV_Vn)Oq&erncF&HAZXKQ-&8X8qLcKQ-&8X8);KKQ-&8X8qKxpPKbkvwmvUPtE$N zSwA)Fr)K@ste=|oQ?q_*)=$m)saZcY>!)V@)U2PH^;5HcYSvH9`kB+yKR=TA1AKiP2pq*e?nf3l&LqVYAg6iV`?8)_+(tlem+T~H%yHyUael&sxoNGH@t z{$xWsp=9kwLpq^k?M6d7p=9kwLoJAswHpn!AWGJ5G}MA9S-a8DxbO8U!3l5ZiAnjK z%U^Z*yi0Jx8_u6>sKs%Itlem+#Zj_$qoEc@`3;w>-Ds%AQL=WUp%zEU+Kq-<93^Wv z8hR#jW!7#q)cV-Z+Kq-!n7HryrO!n7HryrOZ8`(CX$v-O!KN+Pv;~{CVAB?C+Ja5ROpW~7f=yenX$v-O z!KN+Pv;~{CVAB?C+Ja46uxSf6ZNa83*t7+kwqVm1Y}$fNTd-*hHf_PCEjX`eb8RrM zI711>^1SSLKqc7P^RgNx7|Zjr8YLLZ^Nu<5iaCSYroS`q{?5GO412&>p4WS9MJ3qU z^NKuN8I0w5*^3g4<#}0+Z7`PSWjnUN;Om32JTHq-g0Va=i%^2GJTHq-(i@tWPS^%x zdEPyvd1-*_fU!KUh{Tn_Se{obq6A}kUfSSSFqY?~4N5SUBl~k?e~#?Wk^MQcKS%cG z$o?GJpCkKoWPgtA&yoE(vOh=m=g9u770dGH$o?GJpCkKoWPgtA&yoE(vOh=m=g9sX z*`Fi(b7X&x?9Y+?IkG=T_UFj{9NC{E`*UP}j_l8o{W-EfNA~B){v6q#Bl~k?e~#?W zk^MQcKS%cG$o?GJpCkKoWPgtA&yoE(vOh=m=g9sX*`Fi(b7X&x?9Y+?IkG=T_UFj{ z9NC{E`*UP}j_l8o{W-EfNA~B){v6q#Bm1-XRuqw1wqVN^Y}tY>Td-vdwrs(cE!eUJ zTee`!7HrvqEnBc<3$|=Qtu~hhTee`!7HrvqEnBc<3$|>*mMz$_1zWaY%NA_ef-PIH zWec`!!Imx9vISeVV9OS4*@7)wuw@IjY{8Z-*s=v%wqVN^Y}tY>Td-vdwrs(cE!eUJ zTee`!7HrvqEnBc<3$|>*mMz$_1zWaY%NA_ef-PIHWec`!!Imx9vISeVV9OS4*@7)w zuw@IjY{8Z-*s=v%wqVN^Y}tZsN2InRQri)!?TFNNL~1)CwH=Y#j!11sq_!hc+Yzbl zh}3pOYC9se9g*6ONNq=?wj)y85vlEn)OJK_J0i6mk=l+(ZAYZGEOH`fJ0i6mk=l+( zZAYZGBU0NDsqKi=c0_7BBDEcn+KxzVN2InRQri)!?TFNNL~1)CwH=Y#jz|lB8(ffA zPX`Nr8(i?);DS8-fcEg-yCC1@SKfOUtcL}^4KDa?aKUeb3w|41@Y~>m-v$@_Hn`xo z!3Dn!F33x{0`I*G@={9Pdl%%Tl)U#Y$V(}C?_H25u+4k#g5L%g{5H7Yx4{L!4KDa? za6xwGK6&q5@Y~>me1JW?_b$lp(?Q3Qb}VVfl6EX<$C7p|X~&XwENRD*b}VVfl6EX< z$C7p|X~&XwENRD*b}VVfl6EX<$C7p|X~&XwENRD*b}VVfl6EX<$C7p|X~&XwENRD* zb}VVfl6EX<$C7p|X~&XwEos-1b}ebwl6Ea=*OGQEY1fi=Eos-1b}ebwl6Ea=*OGQE zY1fi=Eos-1b}ebwl6Ea=*OGQEY1fi=Eos-1b}ebwl6Ea=*OGQEY1fi=Eos-1b}ebw zl6Ea=*OGQEsh9&5jblgS*wHw4G>#pOV@KoI(KvQAjvb9-N8{MhICeCS9gSm0S9XdF8l$BxFaqjBtL96K7vj>fU0aqMUuI~vE1#<8Pu>}VW28pn>tv7>S9 zXdF8l$BxFaqjBtL96K7vj>fU0aqMUuI~vE1#<8Pu>}VW28pn>tv7>S9XdF8l$BxFa zqjBtL96K7vj>fU0aqMUuI~vE1#<8Pu>}VW28pn>tv7>S9XdF8l$BxFaqjBtL96K7v zj>fU0aqMUuI~p&_S9b=B@={7>QWj;$X_X)gEy{vbm8=_Dl4H?$}Vvdy}oMR~7Q zTX`mBQ9gV?+svda%6o-JAire~GbxLT%#_TeEXsD&!c5Ad{Fyz>q%3+SWl`2-KQk$d z@^gM=CS_6fqDIyYEy`Y$tQ%UCy(n2Xv?zN~vTkV6GbxLnNm=wv%A#5Ucg0M~qO?cJ zOvU?i|MX-K#xwm~$@aXE#_qJCC8&_9*+pB_4uKr$cdv&mJP5+u}f+yEJ*4w`} z*tX`Iz3q#G>(|tK+v`@nxOQc4`;uVWW#8&;Um6Tw*6wXzmipQCpX_a~4{pDF_P)Zz z!D4P?w4Ad z^ry0=Y;j+9IDLO%yqtb0GnP$n8qAann+DQ@xuNWMNk2`F4`++%@@O{w$b*CFhbOY* z$30XTL(D#Q`wQpe5Tm1IjOi`@Afo@sA3rTG@34`_EKT8IF#j_ExoJw{9GxWNtcV6;p|wZxHnzc{g$E+^zZDA zI(dVNPG7c^ktT{cE;*6iof*oecW1_O`Ge`YE4B50)ibE?Y`VbwinAcK2jnweQNv zvbvd4L3W!cl(XaIT!tTp3gf%;xuJ4yd?YLNF%D$7_uj=1b7?uZR50ulpCd=vlK65x%n#gAkCY9>SBf0TR z9>wR%rL)hL4~$3|rQB25(m?uAInYo(Q!3?#bo@lIFi|L$lXO!rt^)X0P;{-Ip7d9(RuD+N{P7k!AyF0Hk-$o^6LGPvpl+4s?JGE7Oq?@=(ke2I5||FEXoy%1;mh? z<;<>JK37h(f(sWWOXJy6X<)Qmp4h&1>;C=w)fy%oAqIvDV_V;9f8TjaTa!le*?xQ8 z`iF@>!*WHX@5zdVzAFgq=Wl)rE%B%kghPMy=l#u3~IZqUp9$J zgX?phtd8ScYMgYL$|KP;Iz_RMRaeTB{X?J*rw)b(<%KP~wN{OQ-s zosOs;q%5kAvt>S0;y22>U03g4C6V~_b@;8X&$l|}hu_&YA^3kpXFsm{D(QZ>`|;qG zo+WbHhkmhVqNjrHy=blV>)B%MpT3v={tA)eK51AWhb+&ZDw{c0Dx^xgNzv#-m!KUx~v*_mFRcAbed`2QoaUjIHe>1xSc zCao^9_04V>4@%34)o5{8kyewI>{t&y;K7OP+;3i=@t$T%Ix=Z*6WYR~Xooo;m7Fo8 z{o}euUVDf9+qg8BmXD!tVH2t33~I)+`75pMQT;}}v{W>NH>HEmX&*-AJ(c7?ceTxVBw7idy%dQRlFZ=ekHs#5yf$a?c~a9@^-8 zJokn6-nwqCRh0CJXW&CmX}e@AJlcyuL$0x;z33~sKDwe+QZu?jqosfdl*rA#A#024 zCwkwfeOzOg{zhA8`{<<3#K&pfi3f5uWFn>_Q4$Y1x2*phx!d)R>8fY82yBh7j{1Ad z_lCauaXst$NBlRfiCBD(u6Vi*+Ct(($iWu((${9m=Vu*7-rQF6s;}VRh&6k4E_aue z6gfwrj*J3Q?<{g%w#QA6^4;M#a9&%VC*4r@TP}`FJhyKR z?t-H-@=C6SjnDKIBu%0Mt(NFYI~Y*S_~3-%!&d#@@BhiOfi{uEBXmZr8PgHxdi>cI z=ZU`ctl0b6H@%N+{S$ldx#^sx;mzy9d0XNscDB7rvLjwFA)8};qF7$vM86SP=3%|> z!F%^czxacl7X&Ljk`XB5@j$Y5cA0voMmARHsEafXvnp7v5ty~X#mX4GM57{?X$0hQ z@#0=7?$LJy8-jNRKNh?z*ciM!xH`BdxHh;>v*quR{@<&o&<)~b{Bf}xzF!jFtg)M0 z)Q-03uDAMK_%?Ax-7cn|JH+mCr$#fk>&@&ZgrR=7#xXyp2zjqOY==;t?+-p9%;X1y zox&L&4E~G|h9B1Z+Mm^X*dxKCLW+G%Z*!j(QtD@fPWl&uzbI_Z&jwEhnbbvE@$|F7 zUkU!X);ep3Tcb!nAN-x**MjF#8eK}Q5^Bxg(%5=8_=P}gcT=l_-wpmp@T}IK{IB4D zYW(zY@DG(sIu-neTIx{nj3UnOs5PDlemnR#$^`w_!Hi}`cWdPE-v<9K_(!tJ_k+Ko z+^N~%-^-S*;J*urV2`Y}SFJIxcKj={-RB$&N{S3+#m@bj^E{xZ-$CUh3I{28G5EW| z&jddm918xjW>dZw98IlBtquNf@O!C?Q|p5N5WE)rx6~!6OM|Zm|2TD7YJKosWySnf z>hjbTsVh@gDL1AOJg*F>|C)M7@SlQLgZ~_y3jSm8U$oZs8(Kl}&ETIZpZJB~p9KF( z7(SKYuLi#!yc~R6<7eMWZ3w=k5!zpt&A$@-ZOu6Rs?Y?!8vOm>uM1D$mx6zm`Z48_ z|I5_7QX5n6PFQnHNQ{K9hVs z<*#+087`DFLtJ>>Q?GBj_S^2eYvso$i-lD?viWj`UANtF$6Z%=d`f-Su}tr@kz!_F zc7QR1%Z78hyHc*?zb;8`-P`M2nKv&V$`ywu$9Ctl2a=yOz)~1bHg^@(zxU?)*YCxD zUR+dnJUOPE%MT~VTsb;9K9VU;j^#6x<>bfYTBS_Btag}EySpk_^MfC#m8JAIj4wNb z6+7-9d^A{}&y>g2&Q{W1bp*ek-qx9WH4GN4cez2o@q1D-qDrgRC}oh^IqtLe1@Vga zUG%AoUR||r)v?u2tl6-(a`EW8d)MVJx$2VdUV7uD?e$l!fAR9-6&tVEb7k$StKV^A z!}s3#!n;1R@!E~gzx&X;U%qbj_V$~?#T7q zuRs2tPWqYm4!n2vh7~sq-tgu3-TuCx{_(Hfxbyw*d;jm;K-iecRKwZMyBm2iJe_(GPy*gQsr4@%H@f-~P}QAKLYy7w%Yn z$KV~$-tqkp-}~WT{BZrwJMaABUBh?%gS%$8-?;tA_V0b#nNZ|Gkp)E#6d6#@=|lF@^ZJnabVMJrp2&G3OE1z?A`020R&HML(bq97`}P!Hxkp2Fw`nV!(<4CkBid@L|Al1jEtS^nu+7ZX=kD z;5CBP2u>pyjo>qa_X5@nIE-MtfbU}2Zs%gREtXQ;R$; zvb4z2B14OnD`aPpn?+_8d0Av-k&{J67Wr6YW08yXe$SGV_dvEUxxQpPkncdY1Gx^# z9Y5n4adLaf>?N<4tX^_@$>=4Ym#hYI8pvoMpMh)!av8{EAdi78267l?buM|kWbKl( zOU5pF3uG;jt4qcL`3ht!T$noDtM}Y(dO{rV^Y$`r!|w;XAKZR0`@!o6s~?Nrhw-44nIQwAigRc*^KDhcG*KF#iwGB%j z9DOkK!OsT|8{B*_^A)uX+Ab)#F!I622X-smYcTP_!v_l=9DFeF!M_Ll9^89h&^6)R zgLMziJs9`k+kGc84>n_y-k{38$pm>4d z1%ekS*`RlU-34+N=nr5&fcyaN1E>#RK7jZD-UDb4U_F4*1$Q~j<)Cwc%>}*#=nkNA zf$R|L8&roooFPDd0NDZL29Oy*UI1AEV>kM3rV?qH8<-lMbk=I$bTjIQvx!sZG)Jlyaw!@~;?D?FU=Fv7zJ4;wsO@G!x{0}l&4 z9Plu}!~YKZJFKcOzr*_u>pPt9u%*J43NPvvlIVmanvp~^lIVn_5E`sLASQvA1X>bU zNgySGlLSf<7)c-`fsb^nY|MJ#*W|6Q$y;BOr@kgneNB3JReE?;dU(}*g|HRE+zwMA zJcV$!!%+xBA^e1}6T(dhGa69MF?nBH3-unJcFM*Lqr+%iyeQDuPhd~|wblB73PKP-i-gH>g;Y^1y9lmtf(&0)6 z69_~g@PI%A0t*NvAmae3u5;JI*U!+x*R2J3&tW}>^Bl%=_|9QFhwB`sb9l~SIfvsM zhI9DMFN}CPAfMhRpI$4UUMrv8C!gM@mMMlZ*%AhF_{U)%hkN`{ec&C3bsWxd7{}oo zhix3LahS&88HZ&Yj&T^q;TMNp{MU3Y%;NBh!zvD^IE>;i=mVQLT;ec^!y^uhI2__I zh{GQadpO+TFo(k%4r};@q2CQ^58qLHIJ+kdxA&cTKK!WOD@@+-c*Eiihc~@W-oyX= z>v0wC?$fdLNyXMDRU24kPpigf)dHXO8e(|5Vd;jW8-{N9xnbvqn;T|sc)4Naenj6e za>K_B8~3O64HGv!+^}%N!3_g9{M)c^!@UjjHoV(EDmKHj4a+th+c0dyuMN94+}bc} z!>bLeHk{fpYQv`un>Jk9Floc14U0A$+AwJUrs{<~8}4kFv*FE#H5<o^5&`h8LPK z!lcgvR`V&fw4YOaJgm2uO=@9>)v~^#7Ij1|>hy>N+|T_{iO1AdlfK3&wU$%8zQ#87 zHMXhMoKimn<^}p0r__25E2bV+Og*fadRQ^_uwv?A#ni)UAxG3gj;MtkQ42Yu7IH)_ z28USHwx+vGha zXY2;B7(rqLhY=J;Fc?8#1b-3qMUWb>Iqx-~F81GT;6m=d-|P_j!2LQ8Oa>4cKv;zP z80KTJ6+u=6R}oZ2Fcm>m1bqSQ1&|lOT>wWB6h$x;K~Mxg5%ff`6G2V{HxblCFcU#c z1TPV^M6eP;N(3hnl*AwPJ_ITvn268m19}432_Pqcga{5ID2QMnf`ADAA?Sx-AA)=c z?jfj$U>?4x&ryBAMgZ#&q(g8HK{*8D5Ih9X5I{Es+Yn?!a1B88G>a9 zk|8*TpcsN-*wqL8LeLAh$s$L47I<0Oe_7A9m!x#~ru%7E<%vLi@A-~@pZ1V#`DLEr;z&{aTLe9j0mAo+pg$9jIS^gz-BzYlg? zu=}3$)y0UV<3NdO=mTC~qz_nqAoVT%6|Yj%5^u0R@Y}*}3%4!Iw(#1*Y73_=jJELE z!e$GXEljrX*ur89hb;`YOQkayYT>7aD-)(ncrszhgd-D%O!zTj$AlXbW=wc7Va0?K z6GlwHrMa)AxysVpZ|SdinY;ilD0rMs>V4s)V*E+P_>+q9Cl%vQD#o8w zj6bQ^d{UkOOOcqO^p^7*ub&B{D?FpHjKVPrlPmn9u#3Vi3bQD@qOgj>DGH+~e4?<4 z!X*lmC_JLDh{7QXgDCu=u!q7O3UesDp|FO+846=4e4+ijS=9cOx|@pR&^m5?;9}jN z`&nAwI{&0Tf1VD@^IYdL&vky!7@gmDcVUd(EVqYqX%AQFdGdgIO%Lcf@_;kX;6Q`{ z5e7^6D`Av`PZBmsxFlhcghvt8^Bsw}PdUbJ%wUHOEYS| ze3c(QzqB-ZS^61-4Bw( z^-jq;r$fp+IiwtfvT_j0%0VbA2cfJyltaoxIix(4L&`@eFVWok(rae<9drWyV z>%z|_yYMAAzvo}h@4H)`={uL1zGZEli?%MzRmRIUJ{KD=YwKLJbz!!M^ZN<5e0P6% z9Ej!CdoHc_?K={Z64u!&tpT`1YXJJVap1y%3U_l44X%#__huY8@ZZ3G`_n>w>tnwC zg188l+S5W)`@FE-Kz{o^Hl ziV)uZUhtgo&YllE6;O#?|T7Oz2q*>$Jx;Y`e~>tDy|x=1KoEA&?%w@XX{!r2qb9#~|- zjV?Fmb0eAHHkWsnCMQb4-7a_Z(7a9~dr@1;b%GnlHmH4Twf=u&Z|_FeloIk@a_rqY z;@;pAQ02gr152)tA}8#)l#utp2wNf4ze|OQw;~u!tx@@~KAUyrpR%4)vgUolxBHxI d3)lz{Xfc9M412p diff --git a/mobile-app-new/assets/icon/icon.png b/mobile-app-new/assets/icon/icon.png deleted file mode 100644 index 8c584824fc49cd57d620ebba1d97bcbc699c7857..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35043 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 DXW*`z diff --git a/mobile-app-new/assets/images/apple-logo.png b/mobile-app-new/assets/images/apple-logo.png deleted file mode 100644 index 7c5fd14f39e540e0a4fd11cbef2a2acd26578a01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54493 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 diff --git a/mobile-app-new/assets/images/freecodecamp-banner.png b/mobile-app-new/assets/images/freecodecamp-banner.png deleted file mode 100644 index 21174aeb49b46280b7585db375574749b614432d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 159847 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

Ch;RhP|%75*q#7 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

1^R3e-RUEUGWVH+pRdl-x1&Z)W41}Ct~;o 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:lU7k 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 Hu0mjfZ$b4` diff --git a/mobile-app-new/assets/images/google-logo.png b/mobile-app-new/assets/images/google-logo.png deleted file mode 100644 index 1c18833a858410ac67344084ce02a1aa3b8e9aec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122573 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 diff --git a/mobile-app-new/assets/images/logo.png b/mobile-app-new/assets/images/logo.png deleted file mode 100644 index 375c9e295c89c1d844185763daa31a7a67c26e9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25521 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 diff --git a/mobile-app-new/assets/images/placeholder-profile-img.png b/mobile-app-new/assets/images/placeholder-profile-img.png deleted file mode 100644 index d9c61f51d348ba7c6c4da0493e16f33e8439daea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22460 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 bLjQ1CYmm`i6|3_j0>3+&`Whwb4@3R~?c9zu diff --git a/mobile-app-new/assets/learn/a1-chinese.svg b/mobile-app-new/assets/learn/a1-chinese.svg deleted file mode 100644 index f0b3fe49f..000000000 --- a/mobile-app-new/assets/learn/a1-chinese.svg +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/a1-spanish.svg b/mobile-app-new/assets/learn/a1-spanish.svg deleted file mode 100644 index f08bcdbf9..000000000 --- a/mobile-app-new/assets/learn/a1-spanish.svg +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/a2-english.svg b/mobile-app-new/assets/learn/a2-english.svg deleted file mode 100644 index 3c78ec8b3..000000000 --- a/mobile-app-new/assets/learn/a2-english.svg +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/algorithm.svg b/mobile-app-new/assets/learn/algorithm.svg deleted file mode 100644 index 3ae51bfae..000000000 --- a/mobile-app-new/assets/learn/algorithm.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/analytics.svg b/mobile-app-new/assets/learn/analytics.svg deleted file mode 100644 index 76c3f372f..000000000 --- a/mobile-app-new/assets/learn/analytics.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/api.svg b/mobile-app-new/assets/learn/api.svg deleted file mode 100644 index 14bdd5b8a..000000000 --- a/mobile-app-new/assets/learn/api.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/b1-english.svg b/mobile-app-new/assets/learn/b1-english.svg deleted file mode 100644 index 257c04a20..000000000 --- a/mobile-app-new/assets/learn/b1-english.svg +++ /dev/null @@ -1,8 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/c-sharp.svg b/mobile-app-new/assets/learn/c-sharp.svg deleted file mode 100644 index c5d705776..000000000 --- a/mobile-app-new/assets/learn/c-sharp.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/clipboard.svg b/mobile-app-new/assets/learn/clipboard.svg deleted file mode 100644 index 3654a91d4..000000000 --- a/mobile-app-new/assets/learn/clipboard.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/code.svg b/mobile-app-new/assets/learn/code.svg deleted file mode 100644 index 04c4d4f4a..000000000 --- a/mobile-app-new/assets/learn/code.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/mobile-app-new/assets/learn/college-algebra.svg b/mobile-app-new/assets/learn/college-algebra.svg deleted file mode 100644 index f29626a52..000000000 --- a/mobile-app-new/assets/learn/college-algebra.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/d3.svg b/mobile-app-new/assets/learn/d3.svg deleted file mode 100644 index d07179107..000000000 --- a/mobile-app-new/assets/learn/d3.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/database.svg b/mobile-app-new/assets/learn/database.svg deleted file mode 100644 index 91875ce9e..000000000 --- a/mobile-app-new/assets/learn/database.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/graduation.svg b/mobile-app-new/assets/learn/graduation.svg deleted file mode 100644 index 910de1e17..000000000 --- a/mobile-app-new/assets/learn/graduation.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/javascript.svg b/mobile-app-new/assets/learn/javascript.svg deleted file mode 100644 index 3d68eb024..000000000 --- a/mobile-app-new/assets/learn/javascript.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/motivational-quotes.json b/mobile-app-new/assets/learn/motivational-quotes.json deleted file mode 100644 index 300c29f21..000000000 --- a/mobile-app-new/assets/learn/motivational-quotes.json +++ /dev/null @@ -1,716 +0,0 @@ -{ - "motivationalQuotes": [ - { - "quote": "Whatever you are, be a good one.", - "author": "Abraham Lincoln" - }, - { - "quote": "A change in perspective is worth 80 IQ points.", - "author": "Alan Kay" - }, - { - "quote": "The best way to predict the future is to invent it.", - "author": "Alan Kay" - }, - { - "quote": "The future is not laid out on a track. It is something that we can decide, and to the extent that we do not violate any known laws of the universe, we can probably make it work the way that we want to.", - "author": "Alan Kay" - }, - { - "quote": "We can only see a short distance ahead, but we can see plenty there that needs to be done.", - "author": "Alan Turing" - }, - { - "quote": "In the depth of winter, I finally learned that within me there lay an invincible summer.", - "author": "Albert Camus" - }, - { - "quote": "A person who never made a mistake never tried anything new.", - "author": "Albert Einstein" - }, - { - "quote": "Creativity is intelligence having fun.", - "author": "Albert Einstein" - }, - { - "quote": "I have no special talents. I am only passionately curious.", - "author": "Albert Einstein" - }, - { - "quote": "Life is like riding a bicycle. To keep your balance, you must keep moving.", - "author": "Albert Einstein" - }, - { - "quote": "Make everything as simple as possible, but not simpler.", - "author": "Albert Einstein" - }, - { - "quote": "Never memorize something that you can look up.", - "author": "Albert Einstein" - }, - { - "quote": "Once we accept our limits, we go beyond them.", - "author": "Albert Einstein" - }, - { - "quote": "Play is the highest form of research.", - "author": "Albert Einstein" - }, - { - "quote": "We cannot solve our problems with the same thinking we used when we created them.", - "author": "Albert Einstein" - }, - { - "quote": "Wisdom is not a product of schooling but of the lifelong attempt to acquire it.", - "author": "Albert Einstein" - }, - { - "quote": "Your imagination is your preview of life's coming attractions.", - "author": "Albert Einstein" - }, - { - "quote": "There is only one corner of the universe you can be certain of improving, and that's your own self.", - "author": "Aldous Huxley" - }, - { - "quote": "The most common way people give up their power is by thinking they don't have any.", - "author": "Alice Walker" - }, - { - "quote": "Follow your inner moonlight. Don't hide the madness.", - "author": "Allen Ginsberg" - }, - { - "quote": "The most difficult thing is the decision to act. The rest is merely tenacity.", - "author": "Amelia Earhart" - }, - { - "quote": "Life shrinks or expands in proportion with one's courage.", - "author": "Anaïs Nin" - }, - { - "quote": "Weeks of programming can save you hours of planning.", - "author": "Unknown" - }, - { - "quote": "Quality is not an act, it is a habit.", - "author": "Aristotle" - }, - { - "quote": "Start where you are. Use what you have. Do what you can.", - "author": "Arthur Ashe" - }, - { - "quote": "Nothing is impossible, the word itself says \"I'm possible\"!", - "author": "Audrey Hepburn" - }, - { - "quote": "Every strike brings me closer to the next home run.", - "author": "Babe Ruth" - }, - { - "quote": "By failing to prepare, you are preparing to fail.", - "author": "Benjamin Franklin" - }, - { - "quote": "Tell me and I forget. Teach me and I remember. Involve me and I learn.", - "author": "Benjamin Franklin" - }, - { - "quote": "Well done is better than well said.", - "author": "Benjamin Franklin" - }, - { - "quote": "There are no short cuts to any place worth going.", - "author": "Beverly Sills" - }, - { - "quote": "Controlling complexity is the essence of computer programming.", - "author": "Brian Kernighan" - }, - { - "quote": "I fear not the man who has practiced 10,000 kicks once, but I fear the man who has practiced one kick 10,000 times.", - "author": "Bruce Lee" - }, - { - "quote": "There are far, far better things ahead than any we leave behind.", - "author": "C.S. Lewis" - }, - { - "quote": "We are what we believe we are.", - "author": "C.S. Lewis" - }, - { - "quote": "With the possible exception of the equator, everything begins somewhere.", - "author": "C.S. Lewis" - }, - { - "quote": "You are never too old to set another goal, or to dream a new dream.", - "author": "C.S. Lewis" - }, - { - "quote": "Somewhere, something incredible is waiting to be known.", - "author": "Carl Sagan" - }, - { - "quote": "If you're not making mistakes, then you're not making decisions.", - "author": "Catherine Cook" - }, - { - "quote": "Find what you love and let it kill you.", - "author": "Charles Bukowski" - }, - { - "quote": "What matters most is how well you walk through the fire.", - "author": "Charles Bukowski" - }, - { - "quote": "It is not the strongest of the species that survive, nor the most intelligent, but the one most responsive to change.", - "author": "Charles Darwin" - }, - { - "quote": "Life is 10% what happens to you and 90% how you react to it.", - "author": "Charles R. Swindoll" - }, - { - "quote": "You will do foolish things, but do them with enthusiasm.", - "author": "Colette" - }, - { - "quote": "It does not matter how slowly you go as long as you do not stop.", - "author": "Confucius" - }, - { - "quote": "Real knowledge is to know the extent of one's ignorance.", - "author": "Confucius" - }, - { - "quote": "The past cannot be changed. The future is yet in your power.", - "author": "Confucius" - }, - { - "quote": "Looking at code you wrote more than two weeks ago is like looking at code you are seeing for the first time.", - "author": "Dan Hurvitz" - }, - { - "quote": "Someday is not a day of the week.", - "author": "Denise Brennan-Nelson" - }, - { - "quote": "UNIX is simple. It just takes a genius to understand its simplicity.", - "author": "Dennis Ritchie" - }, - { - "quote": "Computers are good at following instructions, but not at reading your mind.", - "author": "Donald Knuth" - }, - { - "quote": "A good programmer is someone who always looks both ways before crossing a one-way street.", - "author": "Doug Linder" - }, - { - "quote": "Tough times never last, but tough people do.", - "author": "Dr. Robert Schuller" - }, - { - "quote": "If things start happening, don't worry, don't stew, just go right along and you'll start happening too.", - "author": "Dr. Seuss" - }, - { - "quote": "Do not go gentle into that good night. Rage, rage against the dying of the light.", - "author": "Dylan Thomas" - }, - { - "quote": "The question of whether computers can think is like the question of whether submarines can swim.", - "author": "E.W. Dijkstra" - }, - { - "quote": "Any code of your own that you haven't looked at for six or more months might as well have been written by someone else.", - "author": "Eagleson's Law" - }, - { - "quote": "Do one thing every day that scares you.", - "author": "Eleanor Roosevelt" - }, - { - "quote": "With the new day comes new strength and new thoughts.", - "author": "Eleanor Roosevelt" - }, - { - "quote": "You must do the things you think you cannot do.", - "author": "Eleanor Roosevelt" - }, - { - "quote": "Light tomorrow with today.", - "author": "Elizabeth Barrett Browning" - }, - { - "quote": "Forever is composed of nows.", - "author": "Emily Dickinson" - }, - { - "quote": "Computer science education cannot make anybody an expert programmer any more than studying brushes and pigment can make somebody an expert painter.", - "author": "Eric Raymond" - }, - { - "quote": "If you don't risk anything, you risk even more.", - "author": "Erica Jong" - }, - { - "quote": "The world breaks everyone, and afterward, many are strong at the broken places.", - "author": "Ernest Hemingway" - }, - { - "quote": "There is nothing noble in being superior to your fellow man; true nobility is being superior to your former self.", - "author": "Ernest Hemingway" - }, - { - "quote": "Never confuse a single defeat with a final defeat.", - "author": "F. Scott Fitzgerald" - }, - { - "quote": "I attribute my success to this - I never gave or took any excuse.", - "author": "Florence Nightingale" - }, - { - "quote": "The best revenge is massive success.", - "author": "Frank Sinatra" - }, - { - "quote": "The only limit to our realization of tomorrow, will be our doubts of today.", - "author": "Franklin D. Roosevelt" - }, - { - "quote": "Right or wrong, it's very pleasant to break something from time to time.", - "author": "Fyodor Dostoevsky" - }, - { - "quote": "The harder I work, the luckier I get.", - "author": "Gary Player" - }, - { - "quote": "Giving up is the only sure way to fail.", - "author": "Gena Showalter" - }, - { - "quote": "The only truly secure system is one that is powered off, cast in a block of concrete and sealed in a lead-lined room with armed guards.", - "author": "Gene Spafford" - }, - { - "quote": "A life spent making mistakes is not only more honorable, but more useful than a life spent doing nothing.", - "author": "George Bernard Shaw" - }, - { - "quote": "First learn computer science and all the theory. Next develop a programming style. Then forget all that and just hack.", - "author": "George Carrette" - }, - { - "quote": "Discovering the unexpected is more important than confirming the known.", - "author": "George Box" - }, - { - "quote": "We only see what we know.", - "author": "Goethe" - }, - { - "quote": "Without hard work, nothing grows but weeds.", - "author": "Gordon B. Hinckley" - }, - { - "quote": "The function of good software is to make the complex appear to be simple.", - "author": "Grady Booch" - }, - { - "quote": "When you know that you're capable of dealing with whatever comes, you have the only security the world has to offer.", - "author": "Harry Browne" - }, - { - "quote": "Pain is inevitable. Suffering is optional.", - "author": "Haruki Murakami" - }, - { - "quote": "Optimism is the faith that leads to achievement. Nothing can be done without hope and confidence.", - "author": "Helen Keller" - }, - { - "quote": "The price of anything is the amount of life you exchange for it.", - "author": "Henry David Thoreau" - }, - { - "quote": "Whether you think you can or think you can't, you're right.", - "author": "Henry Ford" - }, - { - "quote": "The most exciting phrase to hear in science, the one that heralds discoveries, is not 'Eureka!' but 'Now that's funny…'", - "author": "Isaac Asimov" - }, - { - "quote": "We are all failures. At least the best of us are.", - "author": "J.M. Barrie" - }, - { - "quote": "You can't wait for inspiration. You have to go after it with a club.", - "author": "Jack London" - }, - { - "quote": "Don't wish it were easier, wish you were better.", - "author": "Jim Rohn" - }, - { - "quote": "By seeking and blundering we learn.", - "author": "Johann Wolfgang von Goethe" - }, - { - "quote": "Knowing is not enough; we must apply. Wishing is not enough; we must do.", - "author": "Johann Wolfgang von Goethe" - }, - { - "quote": "We first make our habits, then our habits make us.", - "author": "John Dryden" - }, - { - "quote": "The power of imagination makes us infinite.", - "author": "John Muir" - }, - { - "quote": "May you live every day of your life.", - "author": "Jonathan Swift" - }, - { - "quote": "Perseverance is failing 19 times and succeeding the 20th.", - "author": "Julie Andrews" - }, - { - "quote": "The work of today is the history of tomorrow, and we are its makers.", - "author": "Juliette Gordon Low" - }, - { - "quote": "If you reveal your secrets to the wind, you should not blame the wind for revealing them to the trees.", - "author": "Kahlil Gibran" - }, - { - "quote": "Optimism is an occupational hazard of programming; feedback is the treatment.", - "author": "Kent Beck" - }, - { - "quote": "Opportunity does not knock, it presents itself when you beat down the door.", - "author": "Kyle Chandler" - }, - { - "quote": "To iterate is human, to recurse divine.", - "author": "Peter Deutsch" - }, - { - "quote": "A good traveler has no fixed plans and is not intent on arriving.", - "author": "Lao Tzu" - }, - { - "quote": "An ant on the move does more than a dozing ox.", - "author": "Lao Tzu" - }, - { - "quote": "Do the difficult things while they are easy and do the great things while they are small. A journey of a thousand miles must begin with a single step.", - "author": "Lao Tzu" - }, - { - "quote": "That's the thing about people who think they hate computers. What they really hate is lousy programmers.", - "author": "Larry Niven" - }, - { - "quote": "It had long since come to my attention that people of accomplishment rarely sat back and let things happen to them. They went out and happened to things.", - "author": "Leonardo da Vinci" - }, - { - "quote": "If you're any good at all, you know you can be better.", - "author": "Lindsay Buckingham" - }, - { - "quote": "If people never did silly things, nothing intelligent would ever get done.", - "author": "Ludwig Wittgenstein" - }, - { - "quote": "You only live once, but if you do it right, once is enough.", - "author": "Mae West" - }, - { - "quote": "Live as if you were to die tomorrow. Learn as if you were to live forever.", - "author": "Mahatma Gandhi" - }, - { - "quote": "Strength does not come from physical capacity. It comes from an indomitable will.", - "author": "Mahatma Gandhi" - }, - { - "quote": "One person's 'paranoia' is another person's 'engineering redundancy'.", - "author": "Marcus J. Ranum" - }, - { - "quote": "Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less.", - "author": "Marie Curie" - }, - { - "quote": "If you have everything under control, you're not moving fast enough.", - "author": "Mario Andretti" - }, - { - "quote": "Education: the path from cocky ignorance to miserable uncertainty.", - "author": "Mark Twain" - }, - { - "quote": "It ain't what you don't know that gets you into trouble. It's what you know for sure that just ain't so.", - "author": "Mark Twain" - }, - { - "quote": "The secret of getting ahead is getting started.", - "author": "Mark Twain" - }, - { - "quote": "The two most important days in your life are the day you are born and the day you find out why.", - "author": "Mark Twain" - }, - { - "quote": "Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails.", - "author": "Mark Twain" - }, - { - "quote": "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", - "author": "Martin Fowler" - }, - { - "quote": "I know, somehow, that only when it is dark enough can you see the stars.", - "author": "Martin Luther King Jr." - }, - { - "quote": "It is never too late to be what you might have been.", - "author": "Mary Anne Evans" - }, - { - "quote": "Nothing will work unless you do.", - "author": "Maya Angelou" - }, - { - "quote": "We delight in the beauty of the butterfly, but rarely admit the changes it has gone through to achieve that beauty.", - "author": "Maya Angelou" - }, - { - "quote": "We may encounter many defeats, but we must not be defeated.", - "author": "Maya Angelou" - }, - { - "quote": "Everybody has talent, but ability takes hard work.", - "author": "Michael Jordan" - }, - { - "quote": "I've missed more than 9,000 shots during my career. I've lost almost 300 games. 26 times, I've been trusted to take the game winning shot and missed. I've failed over and over and over again in my life. And that is why I succeed.", - "author": "Michael Jordan" - }, - { - "quote": "Impossible is just a big word thrown around by small men who find it easier to live in the world they've been given than to explore the power they have to change it. Impossible is not a fact. It's an opinion. Impossible is not a declaration. It's a dare. Impossible is potential. Impossible is temporary. Impossible is nothing.", - "author": "Muhammad Ali" - }, - { - "quote": "A winner is a dreamer who never gives up.", - "author": "Nelson Mandela" - }, - { - "quote": "It always seems impossible until it's done.", - "author": "Nelson Mandela" - }, - { - "quote": "Failure will never overtake me if my determination to succeed is strong enough.", - "author": "Og Mandino" - }, - { - "quote": "I am not young enough to know everything.", - "author": "Oscar Wilde" - }, - { - "quote": "There is only one thing that makes a dream impossible to achieve: the fear of failure.", - "author": "Paulo Coelho" - }, - { - "quote": "Never go to bed mad. Stay up and fight.", - "author": "Phyllis Diller" - }, - { - "quote": "You can't cross the sea merely by standing and staring at the water.", - "author": "Rabindranath Tagore" - }, - { - "quote": "The only person you are destined to become is the person you decide to be.", - "author": "Ralph Waldo Emerson" - }, - { - "quote": "What you do speaks so loudly that I cannot hear what you say.", - "author": "Ralph Waldo Emerson" - }, - { - "quote": "People who are crazy enough to think they can change the world, are the ones who do.", - "author": "Rob Siltanen" - }, - { - "quote": "The best way out is always through.", - "author": "Robert Frost" - }, - { - "quote": "Today's accomplishments were yesterday's impossibilities.", - "author": "Robert H. Schuller" - }, - { - "quote": "Don't be satisfied with stories, how things have gone with others. Unfold your own myth.", - "author": "Rumi" - }, - { - "quote": "Forget safety. Live where you fear to live. Destroy your reputation. Be notorious.", - "author": "Rumi" - }, - { - "quote": "Sell your cleverness and buy bewilderment.", - "author": "Rumi" - }, - { - "quote": "The cure for pain is in the pain.", - "author": "Rumi" - }, - { - "quote": "Have no fear of perfection - you'll never reach it.", - "author": "Salvador Dalí" - }, - { - "quote": "Don't watch the clock. Do what it does. Keep going.", - "author": "Sam Levenson" - }, - { - "quote": "Ever Tried. Ever failed. No matter. Try again. Fail again. Fail better.", - "author": "Samuel Beckett" - }, - { - "quote": "The more you know, the more you realize you know nothing.", - "author": "Socrates" - }, - { - "quote": "The greatest enemy of knowledge is not ignorance, it is the illusion of knowledge.", - "author": "Stephen Hawking" - }, - { - "quote": "The universe doesn't allow perfection.", - "author": "Stephen Hawking" - }, - { - "quote": "Whether you want to uncover the secrets of the universe, or you want to pursue a career in the 21st century, basic computer programming is an essential skill to learn.", - "author": "Stephen Hawking" - }, - { - "quote": "The scariest moment is always just before you start.", - "author": "Stephen King" - }, - { - "quote": "You can, you should, and if you're brave enough to start, you will.", - "author": "Stephen King" - }, - { - "quote": "Arise, Awake and Stop not until the goal is reached.", - "author": "Swami Vivekananda" - }, - { - "quote": "It is said that your life flashes before your eyes just before you die. That is true, it's called Life.", - "author": "Terry Pratchett" - }, - { - "quote": "Believe you can and you're halfway there.", - "author": "Theodore Roosevelt" - }, - { - "quote": "I have not failed. I've just found 10,000 ways that won't work.", - "author": "Thomas A. Edison" - }, - { - "quote": "Our greatest weakness lies in giving up. The most certain way to succeed is always to try just one more time.", - "author": "Thomas A. Edison" - }, - { - "quote": "The harder the conflict, the more glorious the triumph.", - "author": "Thomas Paine" - }, - { - "quote": "The Web as I envisaged it, we have not seen it yet. The future is still so much bigger than the past.", - "author": "Tim Berners-Lee" - }, - { - "quote": "Failure is the condiment that gives success its flavor.", - "author": "Truman Capote" - }, - { - "quote": "Those who says it cannot be done should not interrupt the person doing it.", - "author": "Unknown" - }, - { - "quote": "Even if you fall on your face, you're still moving forward.", - "author": "Victor Kiam" - }, - { - "quote": "It's not whether you get knocked down, it's whether you get up.", - "author": "Vince Lombardi" - }, - { - "quote": "I dream my painting and I paint my dream.", - "author": "Vincent van Gogh" - }, - { - "quote": "Let us cultivate our garden.", - "author": "Voltaire" - }, - { - "quote": "Aim for the moon. If you miss, you may hit a star.", - "author": "W. Clement Stone" - }, - { - "quote": "The way to get started is to quit talking and begin doing.", - "author": "Walt Disney" - }, - { - "quote": "You miss 100% of the shots you don't take.", - "author": "Wayne Gretzky" - }, - { - "quote": "Don't let yesterday take up too much of today.", - "author": "Will Rogers" - }, - { - "quote": "Even if you're on the right track, you'll get run over if you just sit there.", - "author": "Will Rogers" - }, - { - "quote": "Do not wait to strike till the iron is hot; but make it hot by striking.", - "author": "William Butler Yeats" - }, - { - "quote": "You cannot swim for new horizons until you have courage to lose sight of the shore.", - "author": "William Faulkner" - }, - { - "quote": "Be not afraid of greatness. Some are born great, some achieve greatness, and others have greatness thrust upon them.", - "author": "William Shakespeare" - }, - { - "quote": "We know what we are, but not what we may be.", - "author": "William Shakespeare" - }, - { - "quote": "In theory there is no difference between theory and practice. In practice there is.", - "author": "Yogi Berra" - }, - { - "quote": "You can see a lot by just looking.", - "author": "Yogi Berra" - }, - { - "quote": "There is no elevator to success, you have to take the stairs.", - "author": "Zig Ziglar" - }, - { - "quote": "You don't have to be great to start, but you have to start to be great.", - "author": "Zig Ziglar" - } - ] -} diff --git a/mobile-app-new/assets/learn/python.svg b/mobile-app-new/assets/learn/python.svg deleted file mode 100644 index e3d0c1676..000000000 --- a/mobile-app-new/assets/learn/python.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/react.svg b/mobile-app-new/assets/learn/react.svg deleted file mode 100644 index 13d6c9be3..000000000 --- a/mobile-app-new/assets/learn/react.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/responsive-design.svg b/mobile-app-new/assets/learn/responsive-design.svg deleted file mode 100644 index 28ae632d3..000000000 --- a/mobile-app-new/assets/learn/responsive-design.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/rosetta-code.svg b/mobile-app-new/assets/learn/rosetta-code.svg deleted file mode 100644 index 2a45a1cfd..000000000 --- a/mobile-app-new/assets/learn/rosetta-code.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/shield.svg b/mobile-app-new/assets/learn/shield.svg deleted file mode 100644 index 9d4c353d0..000000000 --- a/mobile-app-new/assets/learn/shield.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/tensorflow.svg b/mobile-app-new/assets/learn/tensorflow.svg deleted file mode 100644 index 244779346..000000000 --- a/mobile-app-new/assets/learn/tensorflow.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/learn/viking-helmet.svg b/mobile-app-new/assets/learn/viking-helmet.svg deleted file mode 100644 index c6f7f8866..000000000 --- a/mobile-app-new/assets/learn/viking-helmet.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/mobile-app-new/assets/test_runner/VERSION.txt b/mobile-app-new/assets/test_runner/VERSION.txt deleted file mode 100644 index f7ee06693..000000000 --- a/mobile-app-new/assets/test_runner/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -9.0.0 diff --git a/mobile-app-new/assets/test_runner/babel/babel.min.js b/mobile-app-new/assets/test_runner/babel/babel.min.js deleted file mode 100644 index 795374996..000000000 --- a/mobile-app-new/assets/test_runner/babel/babel.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Babel={})}(this,(function(e){"use strict";function t(e,t){return t.forEach((function(t){t&&"string"!=typeof t&&!Array.isArray(t)&&Object.keys(t).forEach((function(r){if("default"!==r&&!(r in e)){var a=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(e,r,a.get?a:{enumerable:!0,get:function(){return t[r]}})}}))})),Object.freeze(e)}var r=Object.freeze({__proto__:null,get DEFAULT_EXTENSIONS(){return FU},get File(){return dD},get buildExternalHelpers(){return OD},get createConfigItem(){return BF},get createConfigItemAsync(){return OF},get createConfigItemSync(){return NF},get getEnv(){return YD},get loadOptions(){return IF},get loadOptionsAsync(){return CF},get loadOptionsSync(){return _F},get loadPartialConfig(){return PF},get loadPartialConfigAsync(){return SF},get loadPartialConfigSync(){return TF},get parse(){return NU},get parseAsync(){return MU},get parseSync(){return BU},get resolvePlugin(){return KD},get resolvePreset(){return zD},get template(){return tS},get tokTypes(){return vE},get transform(){return wU},get transformAsync(){return TU},get transformFile(){return PU},get transformFileAsync(){return kU},get transformFileSync(){return AU},get transformFromAst(){return _U},get transformFromAstAsync(){return DU},get transformFromAstSync(){return IU},get transformSync(){return SU},get traverse(){return IP},get types(){return Pu},get version(){return LU}});function a(){a=function(){return t};var e,t={},r=Object.prototype,n=r.hasOwnProperty,s=Object.defineProperty||function(e,t,r){e[t]=r.value},i="function"==typeof Symbol?Symbol:{},o=i.iterator||"@@iterator",d=i.asyncIterator||"@@asyncIterator",c=i.toStringTag||"@@toStringTag";function l(e,t,r){return Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{l({},"")}catch(e){l=function(e,t,r){return e[t]=r}}function u(e,t,r,a){var n=t&&t.prototype instanceof v?t:v,i=Object.create(n.prototype),o=new _(a||[]);return s(i,"_invoke",{value:P(e,r,o)}),i}function p(e,t,r){try{return{type:"normal",arg:e.call(t,r)}}catch(e){return{type:"throw",arg:e}}}t.wrap=u;var f="suspendedStart",g="suspendedYield",y="executing",m="completed",h={};function v(){}function b(){}function R(){}var x={};l(x,o,(function(){return this}));var j=Object.getPrototypeOf,E=j&&j(j(I([])));E&&E!==r&&n.call(E,o)&&(x=E);var w=R.prototype=v.prototype=Object.create(x);function S(e){["next","throw","return"].forEach((function(t){l(e,t,(function(e){return this._invoke(t,e)}))}))}function T(e,t){function r(a,s,i,o){var d=p(e[a],e,s);if("throw"!==d.type){var c=d.arg,l=c.value;return l&&"object"==typeof l&&n.call(l,"__await")?t.resolve(l.__await).then((function(e){r("next",e,i,o)}),(function(e){r("throw",e,i,o)})):t.resolve(l).then((function(e){c.value=e,i(c)}),(function(e){return r("throw",e,i,o)}))}o(d.arg)}var a;s(this,"_invoke",{value:function(e,n){function s(){return new t((function(t,a){r(e,n,t,a)}))}return a=a?a.then(s,s):s()}})}function P(t,r,a){var n=f;return function(s,i){if(n===y)throw new Error("Generator is already running");if(n===m){if("throw"===s)throw i;return{value:e,done:!0}}for(a.method=s,a.arg=i;;){var o=a.delegate;if(o){var d=A(o,a);if(d){if(d===h)continue;return d}}if("next"===a.method)a.sent=a._sent=a.arg;else if("throw"===a.method){if(n===f)throw n=m,a.arg;a.dispatchException(a.arg)}else"return"===a.method&&a.abrupt("return",a.arg);n=y;var c=p(t,r,a);if("normal"===c.type){if(n=a.done?m:g,c.arg===h)continue;return{value:c.arg,done:a.done}}"throw"===c.type&&(n=m,a.method="throw",a.arg=c.arg)}}}function A(t,r){var a=r.method,n=t.iterator[a];if(n===e)return r.delegate=null,"throw"===a&&t.iterator.return&&(r.method="return",r.arg=e,A(t,r),"throw"===r.method)||"return"!==a&&(r.method="throw",r.arg=new TypeError("The iterator does not provide a '"+a+"' method")),h;var s=p(n,t.iterator,r.arg);if("throw"===s.type)return r.method="throw",r.arg=s.arg,r.delegate=null,h;var i=s.arg;return i?i.done?(r[t.resultName]=i.value,r.next=t.nextLoc,"return"!==r.method&&(r.method="next",r.arg=e),r.delegate=null,h):i:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,h)}function k(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function C(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function _(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(k,this),this.reset(!0)}function I(t){if(t||""===t){var r=t[o];if(r)return r.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var a=-1,s=function r(){for(;++a=0;--s){var i=this.tryEntries[s],o=i.completion;if("root"===i.tryLoc)return a("end");if(i.tryLoc<=this.prev){var d=n.call(i,"catchLoc"),c=n.call(i,"finallyLoc");if(d&&c){if(this.prev=0;--r){var a=this.tryEntries[r];if(a.tryLoc<=this.prev&&n.call(a,"finallyLoc")&&this.prev=0;--t){var r=this.tryEntries[t];if(r.finallyLoc===e)return this.complete(r.completion,r.afterLoc),C(r),h}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var r=this.tryEntries[t];if(r.tryLoc===e){var a=r.completion;if("throw"===a.type){var n=a.arg;C(r)}return n}}throw new Error("illegal catch attempt")},delegateYield:function(t,r,a){return this.delegate={iterator:I(t),resultName:r,nextLoc:a},"next"===this.method&&(this.arg=e),h}},t}function n(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var a=r.call(e,t||"default");if("object"!=typeof a)return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}function s(e,t,r,a,n,s,i){try{var o=e[s](i),d=o.value}catch(e){return void r(e)}o.done?t(d):Promise.resolve(d).then(a,n)}function i(e){return function(){var t=this,r=arguments;return new Promise((function(a,n){var i=e.apply(t,r);function o(e){s(i,a,n,o,d,"next",e)}function d(e){s(i,a,n,o,d,"throw",e)}o(void 0)}))}}function o(e,t){for(var r=0;r=0||(n[r]=e[r]);return n}function y(e,t){if(null==e)return{};var r,a,n=g(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(n[r]=e[r])}return n}function m(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function h(e,t){return t||(t=e.slice(0)),e.raw=t,e}function v(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var r=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=r){var a,n,s,i,o=[],d=!0,c=!1;try{if(s=(r=r.call(e)).next,0===t){if(Object(r)!==r)return;d=!1}else for(;!(d=(a=s.call(r)).done)&&(o.push(a.value),o.length!==t);d=!0);}catch(e){c=!0,n=e}finally{try{if(!d&&null!=r.return&&(i=r.return(),Object(i)!==i))return}finally{if(c)throw n}}return o}}(e,t)||R(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function b(e){return function(e){if(Array.isArray(e))return x(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||R(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function R(e,t){if(e){if("string"==typeof e)return x(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?x(e,t):void 0}}function x(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,a=new Array(t);r=e.length?{done:!0}:{done:!1,value:e[a++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var E=Object.freeze({__proto__:null,BindingIdentifier:["Identifier"],BlockScoped:null,ExistentialTypeParam:["ExistsTypeAnnotation"],Expression:["Expression"],Flow:["Flow","ImportDeclaration","ExportDeclaration","ImportSpecifier"],ForAwaitStatement:["ForOfStatement"],Generated:null,NumericLiteralTypeAnnotation:["NumberLiteralTypeAnnotation"],Pure:null,Referenced:null,ReferencedIdentifier:["Identifier","JSXIdentifier"],ReferencedMemberExpression:["MemberExpression"],RestProperty:["RestElement"],Scope:["Scopable","Pattern"],SpreadProperty:["RestElement"],Statement:["Statement"],User:null,Var:["VariableDeclaration"]});function w(e,t){for(var r=0,a=Object.keys(t);rn.length)return!1;for(var i=0,o=s.length-1;i1)for(var r=1;re)return!1;if((r+=t[a+1])>=e)return!0}return!1}function qr(e){return e<65?36===e:e<=90||(e<97?95===e:e<=122||(e<=65535?e>=170&&Br.test(String.fromCharCode(e)):Ur(e,Lr)))}function Wr(e){return e<48?36===e:e<58||!(e<65)&&(e<=90||(e<97?95===e:e<=122||(e<=65535?e>=170&&Mr.test(String.fromCharCode(e)):Ur(e,Lr)||Ur(e,Fr))))}function Gr(e){for(var t=!0,r=0;r=48&&e<=57},ra={decBinOct:new Set([46,66,69,79,95,98,101,111]),hex:new Set([46,88,95,120])},aa={bin:function(e){return 48===e||49===e},oct:function(e){return e>=48&&e<=55},dec:function(e){return e>=48&&e<=57},hex:function(e){return e>=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}};function na(e,t,r,a,n,s){for(var i=r,o=a,d=n,c="",l=null,u=r,p=t.length;;){if(r>=p){s.unterminated(i,o,d),c+=t.slice(u,r);break}var f=t.charCodeAt(r);if(sa(e,f,t,r)){c+=t.slice(u,r);break}if(92===f){c+=t.slice(u,r);var g=ia(t,r,a,n,"template"===e,s);null!==g.ch||l?c+=g.ch:l={pos:r,lineStart:a,curLine:n},r=g.pos,a=g.lineStart,n=g.curLine,u=r}else 8232===f||8233===f?(++n,a=++r):10===f||13===f?"template"===e?(c+=t.slice(u,r)+"\n",++r,13===f&&10===t.charCodeAt(r)&&++r,++n,u=a=r):s.unterminated(i,o,d):++r}return{pos:r,str:c,firstInvalidLoc:l,lineStart:a,curLine:n,containsInvalid:!!l}}function sa(e,t,r,a){return"template"===e?96===t||36===t&&123===r.charCodeAt(a+1):t===("double"===e?34:39)}function ia(e,t,r,a,n,s){var i=!n;t++;var o=function(e){return{pos:t,ch:e,lineStart:r,curLine:a}},d=e.charCodeAt(t++);switch(d){case 110:return o("\n");case 114:return o("\r");case 120:var c,l=oa(e,t,r,a,2,!1,i,s);return c=l.code,t=l.pos,o(null===c?null:String.fromCharCode(c));case 117:var u,p=ca(e,t,r,a,i,s);return u=p.code,t=p.pos,o(null===u?null:String.fromCodePoint(u));case 116:return o("\t");case 98:return o("\b");case 118:return o("\v");case 102:return o("\f");case 13:10===e.charCodeAt(t)&&++t;case 10:r=t,++a;case 8232:case 8233:return o("");case 56:case 57:if(n)return o(null);s.strictNumericEscape(t-1,r,a);default:if(d>=48&&d<=55){var f=t-1,g=e.slice(f,t+2).match(/^[0-7]+/)[0],y=parseInt(g,8);y>255&&(g=g.slice(0,-1),y=parseInt(g,8)),t+=g.length-1;var m=e.charCodeAt(t);if("0"!==g||56===m||57===m){if(n)return o(null);s.strictNumericEscape(f,r,a)}return o(String.fromCharCode(y))}return o(String.fromCharCode(d))}}function oa(e,t,r,a,n,s,i,o){var d,c=t,l=da(e,t,r,a,16,n,s,!1,o,!i);return d=l.n,t=l.pos,null===d&&(i?o.invalidEscapeSequence(c,r,a):t=c-1),{code:d,pos:t}}function da(e,t,r,a,n,s,i,o,d,c){for(var l=t,u=16===n?ra.hex:ra.decBinOct,p=16===n?aa.hex:10===n?aa.dec:8===n?aa.oct:aa.bin,f=!1,g=0,y=0,m=null==s?1/0:s;y=97?h-97+10:h>=65?h-65+10:ta(h)?h-48:1/0)>=n){if(v<=9&&c)return{n:null,pos:t};if(v<=9&&d.invalidDigit(t,r,a,n))v=0;else{if(!i)break;v=0,f=!0}}++t,g=g*n+v}else{var b=e.charCodeAt(t-1),R=e.charCodeAt(t+1);if(o){if(Number.isNaN(R)||!p(R)||u.has(b)||u.has(R)){if(c)return{n:null,pos:t};d.unexpectedNumericSeparator(t,r,a)}}else{if(c)return{n:null,pos:t};d.numericSeparatorInEscapeSequence(t,r,a)}++t}}return t===l||null!=s&&t-l!==s||f?{n:null,pos:t}:{n:g,pos:t}}function ca(e,t,r,a,n,s){var i;if(123===e.charCodeAt(t)){var o=oa(e,++t,r,a,e.indexOf("}",t)-t,!0,n,s);if(i=o.code,t=o.pos,++t,null!==i&&i>1114111){if(!n)return{code:null,pos:t};s.invalidCodePoint(t,r,a)}}else{var d=oa(e,t,r,a,4,!1,n,s);i=d.code,t=d.pos}return{code:i,pos:t}}var la=["consequent","body","alternate"],ua=["leadingComments","trailingComments","innerComments"],pa=["||","&&","??"],fa=["++","--"],ga=[">","<",">=","<="],ya=["==","===","!=","!=="],ma=[].concat(ya,["in","instanceof"]),ha=[].concat(b(ma),ga),va=["-","/","%","*","**","&","|",">>",">>>","<<","^"],ba=["+"].concat(va,b(ha),["|>"]),Ra=["=","+="].concat(b(va.map((function(e){return e+"="}))),b(pa.map((function(e){return e+"="})))),xa=["delete","!"],ja=["+","-","~"],Ea=["typeof"],wa=["void","throw"].concat(xa,ja,Ea),Sa={optional:["typeAnnotation","typeParameters","returnType"],force:["start","loc","end"]},Ta=Symbol.for("var used to be block scoped"),Pa=Symbol.for("should not be considered a local binding"),Aa={},ka={},Ca={},_a={},Ia={},Da={},Oa={};function Na(e){return Array.isArray(e)?"array":null===e?"null":typeof e}function Ba(e){return{validate:e}}function Ma(e){return"string"==typeof e?Ha(e):Ha.apply(void 0,b(e))}function La(e){return Ba(Ma(e))}function Fa(e){return{validate:e,optional:!0}}function Ua(e){return{validate:Ma(e),optional:!0}}function qa(e){return t=Ma(e),Ja(za("array"),Ga(t));var t}function Wa(e){return Ba(qa(e))}function Ga(e){function t(t,r,a){if(Array.isArray(a))for(var n=0;n=2&&"type"in t[0]&&"array"===t[0].type&&!("each"in t[1]))throw new Error('An assertValueType("array") validator can only be followed by an assertEach(...) validator.');return a}var Ya=["aliases","builder","deprecatedAlias","fields","inherits","visitor","validate"],$a=["default","optional","deprecated","validate"],Qa={};function Za(){for(var e=arguments.length,t=new Array(e),r=0;r0:d&&"object"==typeof d)throw new Error("field defaults can only be primitives or empty arrays currently");a[i]={default:Array.isArray(d)?[]:d,optional:o.optional,deprecated:o.deprecated,validate:o.validate}}for(var c=t.visitor||r.visitor||[],l=t.aliases||r.aliases||[],u=t.builder||r.builder||t.visitor||[],p=0,f=Object.keys(t);p+s+1)throw new TypeError("RestElement must be last element of "+n)}}}),tn("ReturnStatement",{visitor:["argument"],aliases:["Statement","Terminatorless","CompletionStatement"],fields:{argument:{validate:Ha("Expression"),optional:!0}}}),tn("SequenceExpression",{visitor:["expressions"],fields:{expressions:{validate:Ja(za("array"),Ga(Ha("Expression")))}},aliases:["Expression"]}),tn("ParenthesizedExpression",{visitor:["expression"],aliases:["Expression","ExpressionWrapper"],fields:{expression:{validate:Ha("Expression")}}}),tn("SwitchCase",{visitor:["test","consequent"],fields:{test:{validate:Ha("Expression"),optional:!0},consequent:{validate:Ja(za("array"),Ga(Ha("Statement")))}}}),tn("SwitchStatement",{visitor:["discriminant","cases"],aliases:["Statement","BlockParent","Scopable"],fields:{discriminant:{validate:Ha("Expression")},cases:{validate:Ja(za("array"),Ga(Ha("SwitchCase")))}}}),tn("ThisExpression",{aliases:["Expression"]}),tn("ThrowStatement",{visitor:["argument"],aliases:["Statement","Terminatorless","CompletionStatement"],fields:{argument:{validate:Ha("Expression")}}}),tn("TryStatement",{visitor:["block","handler","finalizer"],aliases:["Statement"],fields:{block:{validate:Ja(Ha("BlockStatement"),Object.assign((function(e){if(Tr.env.BABEL_TYPES_8_BREAKING&&!e.handler&&!e.finalizer)throw new TypeError("TryStatement expects either a handler or finalizer, or both")}),{oneOfNodeTypes:["BlockStatement"]}))},handler:{optional:!0,validate:Ha("CatchClause")},finalizer:{optional:!0,validate:Ha("BlockStatement")}}}),tn("UnaryExpression",{builder:["operator","argument","prefix"],fields:{prefix:{default:!0},argument:{validate:Ha("Expression")},operator:{validate:Va.apply(void 0,b(wa))}},visitor:["argument"],aliases:["UnaryLike","Expression"]}),tn("UpdateExpression",{builder:["operator","argument","prefix"],fields:{prefix:{default:!1},argument:{validate:Tr.env.BABEL_TYPES_8_BREAKING?Ha("Identifier","MemberExpression"):Ha("Expression")},operator:{validate:Va.apply(void 0,b(fa))}},visitor:["argument"],aliases:["Expression"]}),tn("VariableDeclaration",{builder:["kind","declarations"],visitor:["declarations"],aliases:["Statement","Declaration"],fields:{declare:{validate:za("boolean"),optional:!0},kind:{validate:Va("var","let","const","using","await using")},declarations:{validate:Ja(za("array"),Ga(Ha("VariableDeclarator")))}},validate:function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING&&Dr("ForXStatement",e,{left:r})&&1!==r.declarations.length)throw new TypeError("Exactly one VariableDeclarator is required in the VariableDeclaration of a "+e.type)}}),tn("VariableDeclarator",{visitor:["id","init"],fields:{id:{validate:function(){if(!Tr.env.BABEL_TYPES_8_BREAKING)return Ha("LVal");var e=Ha("Identifier","ArrayPattern","ObjectPattern"),t=Ha("Identifier");return function(r,a,n){(r.init?e:t)(r,a,n)}}()},definite:{optional:!0,validate:za("boolean")},init:{optional:!0,validate:Ha("Expression")}}}),tn("WhileStatement",{visitor:["test","body"],aliases:["Statement","BlockParent","Loop","While","Scopable"],fields:{test:{validate:Ha("Expression")},body:{validate:Ha("Statement")}}}),tn("WithStatement",{visitor:["object","body"],aliases:["Statement"],fields:{object:{validate:Ha("Expression")},body:{validate:Ha("Statement")}}}),tn("AssignmentPattern",{visitor:["left","right","decorators"],builder:["left","right"],aliases:["Pattern","PatternLike","LVal"],fields:Object.assign({},un(),{left:{validate:Ha("Identifier","ObjectPattern","ArrayPattern","MemberExpression","TSAsExpression","TSSatisfiesExpression","TSTypeAssertion","TSNonNullExpression")},right:{validate:Ha("Expression")},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0}})}),tn("ArrayPattern",{visitor:["elements","typeAnnotation"],builder:["elements"],aliases:["Pattern","PatternLike","LVal"],fields:Object.assign({},un(),{elements:{validate:Ja(za("array"),Ga(Ka("null","PatternLike","LVal")))}})}),tn("ArrowFunctionExpression",{builder:["params","body","async"],visitor:["params","body","returnType","typeParameters"],aliases:["Scopable","Function","BlockParent","FunctionParent","Expression","Pureish"],fields:Object.assign({},rn(),an(),{expression:{validate:za("boolean")},body:{validate:Ha("BlockStatement","Expression")},predicate:{validate:Ha("DeclaredPredicate","InferredPredicate"),optional:!0}})}),tn("ClassBody",{visitor:["body"],fields:{body:{validate:Ja(za("array"),Ga(Ha("ClassMethod","ClassPrivateMethod","ClassProperty","ClassPrivateProperty","ClassAccessorProperty","TSDeclareMethod","TSIndexSignature","StaticBlock")))}}}),tn("ClassExpression",{builder:["id","superClass","body","decorators"],visitor:["id","body","superClass","mixins","typeParameters","superTypeParameters","implements","decorators"],aliases:["Scopable","Class","Expression"],fields:{id:{validate:Ha("Identifier"),optional:!0},typeParameters:{validate:Ha("TypeParameterDeclaration","TSTypeParameterDeclaration","Noop"),optional:!0},body:{validate:Ha("ClassBody")},superClass:{optional:!0,validate:Ha("Expression")},superTypeParameters:{validate:Ha("TypeParameterInstantiation","TSTypeParameterInstantiation"),optional:!0},implements:{validate:Ja(za("array"),Ga(Ha("TSExpressionWithTypeArguments","ClassImplements"))),optional:!0},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0},mixins:{validate:Ha("InterfaceExtends"),optional:!0}}}),tn("ClassDeclaration",{inherits:"ClassExpression",aliases:["Scopable","Class","Statement","Declaration"],fields:{id:{validate:Ha("Identifier"),optional:!0},typeParameters:{validate:Ha("TypeParameterDeclaration","TSTypeParameterDeclaration","Noop"),optional:!0},body:{validate:Ha("ClassBody")},superClass:{optional:!0,validate:Ha("Expression")},superTypeParameters:{validate:Ha("TypeParameterInstantiation","TSTypeParameterInstantiation"),optional:!0},implements:{validate:Ja(za("array"),Ga(Ha("TSExpressionWithTypeArguments","ClassImplements"))),optional:!0},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0},mixins:{validate:Ha("InterfaceExtends"),optional:!0},declare:{validate:za("boolean"),optional:!0},abstract:{validate:za("boolean"),optional:!0}},validate:function(){var e=Ha("Identifier");return function(t,r,a){Tr.env.BABEL_TYPES_8_BREAKING&&(Dr("ExportDefaultDeclaration",t)||e(a,"id",a.id))}}()}),tn("ExportAllDeclaration",{builder:["source"],visitor:["source","attributes","assertions"],aliases:["Statement","Declaration","ImportOrExportDeclaration","ExportDeclaration"],fields:{source:{validate:Ha("StringLiteral")},exportKind:Fa(Va("type","value")),attributes:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},assertions:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))}}}),tn("ExportDefaultDeclaration",{visitor:["declaration"],aliases:["Statement","Declaration","ImportOrExportDeclaration","ExportDeclaration"],fields:{declaration:{validate:Ha("TSDeclareFunction","FunctionDeclaration","ClassDeclaration","Expression")},exportKind:Fa(Va("value"))}}),tn("ExportNamedDeclaration",{builder:["declaration","specifiers","source"],visitor:["declaration","specifiers","source","attributes","assertions"],aliases:["Statement","Declaration","ImportOrExportDeclaration","ExportDeclaration"],fields:{declaration:{optional:!0,validate:Ja(Ha("Declaration"),Object.assign((function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING&&r&&e.specifiers.length)throw new TypeError("Only declaration or specifiers is allowed on ExportNamedDeclaration")}),{oneOfNodeTypes:["Declaration"]}),(function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING&&r&&e.source)throw new TypeError("Cannot export a declaration from a source")}))},attributes:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},assertions:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},specifiers:{default:[],validate:Ja(za("array"),Ga((cn=Ha("ExportSpecifier","ExportDefaultSpecifier","ExportNamespaceSpecifier"),ln=Ha("ExportSpecifier"),Tr.env.BABEL_TYPES_8_BREAKING?function(e,t,r){(e.source?cn:ln)(e,t,r)}:cn)))},source:{validate:Ha("StringLiteral"),optional:!0},exportKind:Fa(Va("type","value"))}}),tn("ExportSpecifier",{visitor:["local","exported"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")},exported:{validate:Ha("Identifier","StringLiteral")},exportKind:{validate:Va("type","value"),optional:!0}}}),tn("ForOfStatement",{visitor:["left","right","body"],builder:["left","right","body","await"],aliases:["Scopable","Statement","For","BlockParent","Loop","ForXStatement"],fields:{left:{validate:function(){if(!Tr.env.BABEL_TYPES_8_BREAKING)return Ha("VariableDeclaration","LVal");var e=Ha("VariableDeclaration"),t=Ha("Identifier","MemberExpression","ArrayPattern","ObjectPattern","TSAsExpression","TSSatisfiesExpression","TSTypeAssertion","TSNonNullExpression");return function(r,a,n){Dr("VariableDeclaration",n)?e(r,a,n):t(r,a,n)}}()},right:{validate:Ha("Expression")},body:{validate:Ha("Statement")},await:{default:!1}}}),tn("ImportDeclaration",{builder:["specifiers","source"],visitor:["specifiers","source","attributes","assertions"],aliases:["Statement","Declaration","ImportOrExportDeclaration"],fields:{attributes:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},assertions:{optional:!0,validate:Ja(za("array"),Ga(Ha("ImportAttribute")))},module:{optional:!0,validate:za("boolean")},phase:{default:null,validate:Va("source","defer")},specifiers:{validate:Ja(za("array"),Ga(Ha("ImportSpecifier","ImportDefaultSpecifier","ImportNamespaceSpecifier")))},source:{validate:Ha("StringLiteral")},importKind:{validate:Va("type","typeof","value"),optional:!0}}}),tn("ImportDefaultSpecifier",{visitor:["local"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")}}}),tn("ImportNamespaceSpecifier",{visitor:["local"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")}}}),tn("ImportSpecifier",{visitor:["local","imported"],aliases:["ModuleSpecifier"],fields:{local:{validate:Ha("Identifier")},imported:{validate:Ha("Identifier","StringLiteral")},importKind:{validate:Va("type","typeof","value"),optional:!0}}}),tn("ImportExpression",{visitor:["source","options"],aliases:["Expression"],fields:{phase:{default:null,validate:Va("source","defer")},source:{validate:Ha("Expression")},options:{validate:Ha("Expression"),optional:!0}}}),tn("MetaProperty",{visitor:["meta","property"],aliases:["Expression"],fields:{meta:{validate:Ja(Ha("Identifier"),Object.assign((function(e,t,r){if(Tr.env.BABEL_TYPES_8_BREAKING){var a;switch(r.name){case"function":a="sent";break;case"new":a="target";break;case"import":a="meta"}if(!Dr("Identifier",e.property,{name:a}))throw new TypeError("Unrecognised MetaProperty")}}),{oneOfNodeTypes:["Identifier"]}))},property:{validate:Ha("Identifier")}}});var pn=function(){return{abstract:{validate:za("boolean"),optional:!0},accessibility:{validate:Va("public","private","protected"),optional:!0},static:{default:!1},override:{default:!1},computed:{default:!1},optional:{validate:za("boolean"),optional:!0},key:{validate:Ja(function(){var e=Ha("Identifier","StringLiteral","NumericLiteral","BigIntLiteral"),t=Ha("Expression");return function(r,a,n){(r.computed?t:e)(r,a,n)}}(),Ha("Identifier","StringLiteral","NumericLiteral","BigIntLiteral","Expression"))}}},fn=function(){return Object.assign({},rn(),pn(),{params:{validate:Ja(za("array"),Ga(Ha("Identifier","Pattern","RestElement","TSParameterProperty")))},kind:{validate:Va("get","set","method","constructor"),default:"method"},access:{validate:Ja(za("string"),Va("public","private","protected")),optional:!0},decorators:{validate:Ja(za("array"),Ga(Ha("Decorator"))),optional:!0}})};tn("ClassMethod",{aliases:["Function","Scopable","BlockParent","FunctionParent","Method"],builder:["kind","key","params","body","computed","static","generator","async"],visitor:["key","params","body","decorators","returnType","typeParameters"],fields:Object.assign({},fn(),an(),{body:{validate:Ha("BlockStatement")}})}),tn("ObjectPattern",{visitor:["properties","typeAnnotation","decorators"],builder:["properties"],aliases:["Pattern","PatternLike","LVal"],fields:Object.assign({},un(),{properties:{validate:Ja(za("array"),Ga(Ha("RestElement","ObjectProperty")))}})}),tn("SpreadElement",{visitor:["argument"],aliases:["UnaryLike"],deprecatedAlias:"SpreadProperty",fields:{argument:{validate:Ha("Expression")}}}),tn("Super",{aliases:["Expression"]}),tn("TaggedTemplateExpression",{visitor:["tag","quasi","typeParameters"],builder:["tag","quasi"],aliases:["Expression"],fields:{tag:{validate:Ha("Expression")},quasi:{validate:Ha("TemplateLiteral")},typeParameters:{validate:Ha("TypeParameterInstantiation","TSTypeParameterInstantiation"),optional:!0}}}),tn("TemplateElement",{builder:["value","tail"],fields:{value:{validate:Ja(function(e){function t(t,r,a){for(var n=[],s=0,i=Object.keys(e);s=0)){if(Fe(o))return[o];if(zt(o))a.set(o.type,o);else if($e(o))n.has(o.types)||(t.push.apply(t,b(o.types)),n.add(o.types));else if(Ge(o)){var d=Nc(o.id);if(r.has(d)){var c,l=r.get(d);if(l.typeParameters){if(o.typeParameters)(c=l.typeParameters.params).push.apply(c,b(o.typeParameters.params)),l.typeParameters.params=Bc(l.typeParameters.params)}else l=o.typeParameters}else r.set(d,o)}else s.push(o)}}for(var u,p=j(a);!(u=p()).done;){var f=v(u.value,2)[1];s.push(f)}for(var g,y=j(r);!(g=y()).done;){var m=v(g.value,2)[1];s.push(m)}return s}function Mc(e){var t=Bc(e);return 1===t.length?t[0]:Ro(t)}function Lc(e){return F(e)?e.name:e.right.name+"."+Lc(e.left)}function Fc(e){for(var t=Array.from(e),r=new Map,a=new Map,n=new Set,s=[],i=0;i=0)){if(mt(o))return[o];if(Yt(o))a.set(o.type,o);else if(bt(o))n.has(o.types)||(t.push.apply(t,b(o.types)),n.add(o.types));else if(ht(o)&&o.typeParameters){var d=Lc(o.typeName);if(r.has(d)){var c,l=r.get(d);if(l.typeParameters){if(o.typeParameters)(c=l.typeParameters.params).push.apply(c,b(o.typeParameters.params)),l.typeParameters.params=Fc(l.typeParameters.params)}else l=o.typeParameters}else r.set(d,o)}else s.push(o)}}for(var u,p=j(a);!(u=p()).done;){var f=v(u.value,2)[1];s.push(f)}for(var g,y=j(r);!(g=y()).done;){var m=v(g.value,2)[1];s.push(m)}return s}function Uc(e){var t=e.map((function(e){return Pt(e)?e.typeAnnotation:e})),r=Fc(t);return 1===r.length?r[0]:Kd(r)}function qc(){return Ds("void",fs(0),!0)}var Wc=Function.call.bind(Object.prototype.hasOwnProperty);function Gc(e,t,r,a){return e&&"string"==typeof e.type?Kc(e,t,r,a):e}function Vc(e,t,r,a){return Array.isArray(e)?e.map((function(e){return Gc(e,t,r,a)})):Gc(e,t,r,a)}function Hc(e,t,r){return void 0===t&&(t=!0),void 0===r&&(r=!1),Kc(e,t,r,new Map)}function Kc(e,t,r,a){if(void 0===t&&(t=!0),void 0===r&&(r=!1),!e)return e;var n=e.type,s={type:e.type};if(F(e))s.name=e.name,Wc(e,"optional")&&"boolean"==typeof e.optional&&(s.optional=e.optional),Wc(e,"typeAnnotation")&&(s.typeAnnotation=t?Vc(e.typeAnnotation,!0,r,a):e.typeAnnotation);else{if(!Wc(_a,n))throw new Error('Unknown node type: "'+n+'"');for(var i=0,o=Object.keys(_a[n]);i=Number.MAX_SAFE_INTEGER?cu.uid=0:cu.uid++};var uu=Function.call.bind(Object.prototype.toString);function pu(e){if(void 0===e)return cs("undefined");if(!0===e||!1===e)return ys(e);if(null===e)return{type:"NullLiteral"};if("string"==typeof e)return ps(e);if("number"==typeof e){var t;if(Number.isFinite(e))t=fs(Math.abs(e));else t=Vn("/",Number.isNaN(e)?fs(0):fs(1),fs(0));return(e<0||Object.is(e,-0))&&(t=Ds("-",t)),t}if(function(e){return"[object RegExp]"===uu(e)}(e))return ms(e.source,e.toString().match(/\/([a-z]+|)$/)[1]);if(Array.isArray(e))return Wn(e.map(pu));if(function(e){if("object"!=typeof e||null===e||"[object Object]"!==Object.prototype.toString.call(e))return!1;var t=Object.getPrototypeOf(e);return null===t||null===Object.getPrototypeOf(t)}(e)){for(var r=[],a=0,n=Object.keys(e);a=0)return!0}else if(s===e)return!0}return!1}function bu(e){return se(e)&&("var"!==e.kind||e[Ta])}function Ru(e){return M(e)||pe(e)||bu(e)}function xu(e,t,r){switch(t.type){case"MemberExpression":case"OptionalMemberExpression":return t.property===e?!!t.computed:t.object===e;case"JSXMemberExpression":return t.object===e;case"VariableDeclarator":return t.init===e;case"ArrowFunctionExpression":return t.body===e;case"PrivateName":case"LabeledStatement":case"CatchClause":case"RestElement":case"BreakStatement":case"ContinueStatement":case"FunctionDeclaration":case"FunctionExpression":case"ExportNamespaceSpecifier":case"ExportDefaultSpecifier":case"ImportDefaultSpecifier":case"ImportNamespaceSpecifier":case"ImportSpecifier":case"ImportAttribute":case"JSXAttribute":case"ObjectPattern":case"ArrayPattern":case"MetaProperty":return!1;case"ClassMethod":case"ClassPrivateMethod":case"ObjectMethod":return t.key===e&&!!t.computed;case"ObjectProperty":return t.key===e?!!t.computed:!r||"ObjectPattern"!==r.type;case"ClassProperty":case"ClassAccessorProperty":case"TSPropertySignature":return t.key!==e||!!t.computed;case"ClassPrivateProperty":case"ObjectTypeProperty":return t.key!==e;case"ClassDeclaration":case"ClassExpression":return t.superClass===e;case"AssignmentExpression":case"AssignmentPattern":return t.right===e;case"ExportSpecifier":return(null==r||!r.source)&&t.local===e;case"TSEnumMember":return t.id!==e}return!0}function ju(e,t){return(!C(e)||!Ot(t)&&!I(t))&&(!(!Ut(e)||!Ot(t)&&!I(t))||Ct(e))}gu.keys={DeclareClass:["id"],DeclareFunction:["id"],DeclareModule:["id"],DeclareVariable:["id"],DeclareInterface:["id"],DeclareTypeAlias:["id"],DeclareOpaqueType:["id"],InterfaceDeclaration:["id"],TypeAlias:["id"],OpaqueType:["id"],CatchClause:["param"],LabeledStatement:["label"],UnaryExpression:["argument"],AssignmentExpression:["left"],ImportSpecifier:["local"],ImportNamespaceSpecifier:["local"],ImportDefaultSpecifier:["local"],ImportDeclaration:["specifiers"],ExportSpecifier:["exported"],ExportNamespaceSpecifier:["exported"],ExportDefaultSpecifier:["exported"],FunctionDeclaration:["id","params"],FunctionExpression:["id","params"],ArrowFunctionExpression:["params"],ObjectMethod:["params"],ClassMethod:["params"],ClassPrivateMethod:["params"],ForInStatement:["left"],ForOfStatement:["left"],ClassDeclaration:["id"],ClassExpression:["id"],RestElement:["argument"],UpdateExpression:["argument"],ObjectProperty:["value"],AssignmentPattern:["left"],ArrayPattern:["elements"],ObjectPattern:["properties"],VariableDeclaration:["declarations"],VariableDeclarator:["id"]};var Eu=new Set(["abstract","boolean","byte","char","double","enum","final","float","goto","implements","int","interface","long","native","package","private","protected","public","short","static","synchronized","throws","transient","volatile"]);function wu(e){return ea(e)&&!Eu.has(e)}function Su(e){return se(e,{kind:"var"})&&!e[Ta]}var Tu={isReactComponent:Zt,isCompatTag:function(e){return!!e&&/^[a-z]/.test(e)},buildChildren:function(e){for(var t=[],r=0;r=1.5*r;return Math.round(e/r)+" "+a+(n?"s":"")}return Rp=function(o,d){d=d||{};var c=typeof o;if("string"===c&&o.length>0)return function(i){if((i=String(i)).length>100)return;var o=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(i);if(!o)return;var d=parseFloat(o[1]);switch((o[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return d*s;case"weeks":case"week":case"w":return d*n;case"days":case"day":case"d":return d*a;case"hours":case"hour":case"hrs":case"hr":case"h":return d*r;case"minutes":case"minute":case"mins":case"min":case"m":return d*t;case"seconds":case"second":case"secs":case"sec":case"s":return d*e;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return d;default:return}}(o);if("number"===c&&isFinite(o))return d.long?function(n){var s=Math.abs(n);if(s>=a)return i(n,s,a,"day");if(s>=r)return i(n,s,r,"hour");if(s>=t)return i(n,s,t,"minute");if(s>=e)return i(n,s,e,"second");return n+" ms"}(o):function(n){var s=Math.abs(n);if(s>=a)return Math.round(n/a)+"d";if(s>=r)return Math.round(n/r)+"h";if(s>=t)return Math.round(n/t)+"m";if(s>=e)return Math.round(n/e)+"s";return n+"ms"}(o);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(o))},Rp}var Sp=function(e){function t(e){var a,n,s,i=null;function o(){for(var e=arguments.length,r=new Array(e),n=0;n=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(r=!1,function(){r||(r=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||function(){},e.exports=Sp(t),e.exports.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}}(Ep,Ep.exports);var Tp=Ep.exports,Pp=Hc,Ap=zs,kp=Xs,Cp=cs,_p=Ns,Ip=Bs;function Dp(e){if(!e.isExportDeclaration()||e.isExportAllDeclaration())throw new Error("Only default and named export declarations can be split.");if(e.isExportDefaultDeclaration()){var t=e.get("declaration"),r=t.isFunctionDeclaration()||t.isClassDeclaration(),a=t.isFunctionExpression()||t.isClassExpression(),n=t.isScope()?t.scope.parent:t.scope,s=t.node.id,i=!1;s?a&&n.hasBinding(s.name)&&(i=!0,s=n.generateUidIdentifier(s.name)):(i=!0,s=n.generateUidIdentifier("default"),(r||a)&&(t.node.id=Pp(s)));var o=r?t.node:_p("var",[Ip(Pp(s),t.node)]),d=Ap(null,[kp(Pp(s),Cp("default"))]);return e.insertAfter(d),e.replaceWith(o),i&&n.registerDeclaration(e),e}if(e.get("specifiers").length>0)throw new Error("It doesn't make sense to split exported specifiers.");var c=e.get("declaration"),l=c.getOuterBindingIdentifiers(),u=Object.keys(l).map((function(e){return kp(Cp(e),Cp(e))})),p=Ap(null,u);return e.insertAfter(p),e.replaceWith(c.node),e}function Op(e){var t=e.context,r=e.node;if(r.computed&&t.maybeQueue(e.get("key")),r.decorators)for(var a,n=j(e.get("decorators"));!(a=n()).done;){var s=a.value;t.maybeQueue(s)}}var Np={FunctionParent:function(e){e.isArrowFunctionExpression()||(e.skip(),e.isMethod()&&Op(e))},Property:function(e){e.isObjectProperty()||(e.skip(),Op(e))}},Bp={ReferencedIdentifier:function(e,t){var r=e.node;r.name===t.oldName&&(r.name=t.newName)},Scope:function(e,t){e.scope.bindingIdentifierEquals(t.oldName,t.binding.identifier)||(e.skip(),e.isMethod()&&Op(e))},ObjectProperty:function(e,t){var r,a=e.node,n=e.scope,s=a.key.name;!a.shorthand||s!==t.oldName&&s!==t.newName||n.getBindingIdentifier(s)!==t.binding.identifier||(a.shorthand=!1,null!=(r=a.extra)&&r.shorthand&&(a.extra.shorthand=!1))},"AssignmentExpression|Declaration|VariableDeclarator":function(e,t){if(!e.isVariableDeclaration()){var r=e.getOuterBindingIdentifiers();for(var a in r)a===t.oldName&&(r[a].name=t.newName)}}},Mp=function(){function e(e,t,r){this.newName=r,this.oldName=t,this.binding=e}var t=e.prototype;return t.maybeConvertFromExportDeclaration=function(e){var t=e.parentPath;if(t.isExportDeclaration()){if(t.isExportDefaultDeclaration()){var r=t.node.declaration;if(Bt(r)&&!r.id)return}t.isExportAllDeclaration()||Dp(t)}},t.maybeConvertFromClassFunctionDeclaration=function(e){return e},t.maybeConvertFromClassFunctionExpression=function(e){return e},t.rename=function(){var e=this.binding,t=this.oldName,r=this.newName,a=e.scope,n=e.path,s=n.find((function(e){return e.isDeclaration()||e.isFunctionExpression()||e.isClassExpression()}));s&&(s.getOuterBindingIdentifiers()[t]===e.identifier&&this.maybeConvertFromExportDeclaration(s));AP(arguments[0]||a.block,ap(Bp),a,this,a.path,{discriminant:!0}),arguments[0]||(a.removeOwnBinding(t),a.bindings[r]=e,this.binding.identifier.name=r),s&&(this.maybeConvertFromClassFunctionDeclaration(n),this.maybeConvertFromClassFunctionExpression(n))},d(e)}(),Lp=function(){function e(e){var t=e.identifier,r=e.scope,a=e.path,n=e.kind;this.identifier=void 0,this.scope=void 0,this.path=void 0,this.kind=void 0,this.constantViolations=[],this.constant=!0,this.referencePaths=[],this.referenced=!1,this.references=0,this.identifier=t,this.scope=r,this.path=a,this.kind=n,"var"!==n&&"hoisted"!==n||!function(e){for(var t=e.parentPath,r=e.key;t;t=(a=t).parentPath,r=a.key,a){var a;if(t.isFunctionParent())return!1;if(t.isWhile()||t.isForXStatement()||t.isForStatement()&&"body"===r)return!0}return!1}(a)||this.reassign(a),this.clearValue()}var t=e.prototype;return t.deoptValue=function(){this.clearValue(),this.hasDeoptedValue=!0},t.setValue=function(e){this.hasDeoptedValue||(this.hasValue=!0,this.value=e)},t.clearValue=function(){this.hasDeoptedValue=!1,this.hasValue=!1,this.value=null},t.reassign=function(e){this.constant=!1,-1===this.constantViolations.indexOf(e)&&this.constantViolations.push(e)},t.reference=function(e){-1===this.referencePaths.indexOf(e)&&(this.referenced=!0,this.references++,this.referencePaths.push(e))},t.dereference=function(){this.references--,this.referenced=!!this.references},d(e)}();var Fp,Up,qp={builtin:{Array:!1,ArrayBuffer:!1,Atomics:!1,BigInt:!1,BigInt64Array:!1,BigUint64Array:!1,Boolean:!1,constructor:!1,DataView:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Float32Array:!1,Float64Array:!1,Function:!1,globalThis:!1,hasOwnProperty:!1,Infinity:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Map:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,Promise:!1,propertyIsEnumerable:!1,Proxy:!1,RangeError:!1,ReferenceError:!1,Reflect:!1,RegExp:!1,Set:!1,SharedArrayBuffer:!1,String:!1,Symbol:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1,WeakMap:!1,WeakSet:!1},es5:{Array:!1,Boolean:!1,constructor:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Function:!1,hasOwnProperty:!1,Infinity:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,propertyIsEnumerable:!1,RangeError:!1,ReferenceError:!1,RegExp:!1,String:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1},es2015:{Array:!1,ArrayBuffer:!1,Boolean:!1,constructor:!1,DataView:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Float32Array:!1,Float64Array:!1,Function:!1,hasOwnProperty:!1,Infinity:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Map:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,Promise:!1,propertyIsEnumerable:!1,Proxy:!1,RangeError:!1,ReferenceError:!1,Reflect:!1,RegExp:!1,Set:!1,String:!1,Symbol:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1,WeakMap:!1,WeakSet:!1},es2017:{Array:!1,ArrayBuffer:!1,Atomics:!1,Boolean:!1,constructor:!1,DataView:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,escape:!1,eval:!1,EvalError:!1,Float32Array:!1,Float64Array:!1,Function:!1,hasOwnProperty:!1,Infinity:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,isFinite:!1,isNaN:!1,isPrototypeOf:!1,JSON:!1,Map:!1,Math:!1,NaN:!1,Number:!1,Object:!1,parseFloat:!1,parseInt:!1,Promise:!1,propertyIsEnumerable:!1,Proxy:!1,RangeError:!1,ReferenceError:!1,Reflect:!1,RegExp:!1,Set:!1,SharedArrayBuffer:!1,String:!1,Symbol:!1,SyntaxError:!1,toLocaleString:!1,toString:!1,TypeError:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1,undefined:!1,unescape:!1,URIError:!1,valueOf:!1,WeakMap:!1,WeakSet:!1},browser:{AbortController:!1,AbortSignal:!1,addEventListener:!1,alert:!1,AnalyserNode:!1,Animation:!1,AnimationEffectReadOnly:!1,AnimationEffectTiming:!1,AnimationEffectTimingReadOnly:!1,AnimationEvent:!1,AnimationPlaybackEvent:!1,AnimationTimeline:!1,applicationCache:!1,ApplicationCache:!1,ApplicationCacheErrorEvent:!1,atob:!1,Attr:!1,Audio:!1,AudioBuffer:!1,AudioBufferSourceNode:!1,AudioContext:!1,AudioDestinationNode:!1,AudioListener:!1,AudioNode:!1,AudioParam:!1,AudioProcessingEvent:!1,AudioScheduledSourceNode:!1,"AudioWorkletGlobalScope ":!1,AudioWorkletNode:!1,AudioWorkletProcessor:!1,BarProp:!1,BaseAudioContext:!1,BatteryManager:!1,BeforeUnloadEvent:!1,BiquadFilterNode:!1,Blob:!1,BlobEvent:!1,blur:!1,BroadcastChannel:!1,btoa:!1,BudgetService:!1,ByteLengthQueuingStrategy:!1,Cache:!1,caches:!1,CacheStorage:!1,cancelAnimationFrame:!1,cancelIdleCallback:!1,CanvasCaptureMediaStreamTrack:!1,CanvasGradient:!1,CanvasPattern:!1,CanvasRenderingContext2D:!1,ChannelMergerNode:!1,ChannelSplitterNode:!1,CharacterData:!1,clearInterval:!1,clearTimeout:!1,clientInformation:!1,ClipboardEvent:!1,close:!1,closed:!1,CloseEvent:!1,Comment:!1,CompositionEvent:!1,confirm:!1,console:!1,ConstantSourceNode:!1,ConvolverNode:!1,CountQueuingStrategy:!1,createImageBitmap:!1,Credential:!1,CredentialsContainer:!1,crypto:!1,Crypto:!1,CryptoKey:!1,CSS:!1,CSSConditionRule:!1,CSSFontFaceRule:!1,CSSGroupingRule:!1,CSSImportRule:!1,CSSKeyframeRule:!1,CSSKeyframesRule:!1,CSSMediaRule:!1,CSSNamespaceRule:!1,CSSPageRule:!1,CSSRule:!1,CSSRuleList:!1,CSSStyleDeclaration:!1,CSSStyleRule:!1,CSSStyleSheet:!1,CSSSupportsRule:!1,CustomElementRegistry:!1,customElements:!1,CustomEvent:!1,DataTransfer:!1,DataTransferItem:!1,DataTransferItemList:!1,defaultstatus:!1,defaultStatus:!1,DelayNode:!1,DeviceMotionEvent:!1,DeviceOrientationEvent:!1,devicePixelRatio:!1,dispatchEvent:!1,document:!1,Document:!1,DocumentFragment:!1,DocumentType:!1,DOMError:!1,DOMException:!1,DOMImplementation:!1,DOMMatrix:!1,DOMMatrixReadOnly:!1,DOMParser:!1,DOMPoint:!1,DOMPointReadOnly:!1,DOMQuad:!1,DOMRect:!1,DOMRectReadOnly:!1,DOMStringList:!1,DOMStringMap:!1,DOMTokenList:!1,DragEvent:!1,DynamicsCompressorNode:!1,Element:!1,ErrorEvent:!1,event:!1,Event:!1,EventSource:!1,EventTarget:!1,external:!1,fetch:!1,File:!1,FileList:!1,FileReader:!1,find:!1,focus:!1,FocusEvent:!1,FontFace:!1,FontFaceSetLoadEvent:!1,FormData:!1,frameElement:!1,frames:!1,GainNode:!1,Gamepad:!1,GamepadButton:!1,GamepadEvent:!1,getComputedStyle:!1,getSelection:!1,HashChangeEvent:!1,Headers:!1,history:!1,History:!1,HTMLAllCollection:!1,HTMLAnchorElement:!1,HTMLAreaElement:!1,HTMLAudioElement:!1,HTMLBaseElement:!1,HTMLBodyElement:!1,HTMLBRElement:!1,HTMLButtonElement:!1,HTMLCanvasElement:!1,HTMLCollection:!1,HTMLContentElement:!1,HTMLDataElement:!1,HTMLDataListElement:!1,HTMLDetailsElement:!1,HTMLDialogElement:!1,HTMLDirectoryElement:!1,HTMLDivElement:!1,HTMLDListElement:!1,HTMLDocument:!1,HTMLElement:!1,HTMLEmbedElement:!1,HTMLFieldSetElement:!1,HTMLFontElement:!1,HTMLFormControlsCollection:!1,HTMLFormElement:!1,HTMLFrameElement:!1,HTMLFrameSetElement:!1,HTMLHeadElement:!1,HTMLHeadingElement:!1,HTMLHRElement:!1,HTMLHtmlElement:!1,HTMLIFrameElement:!1,HTMLImageElement:!1,HTMLInputElement:!1,HTMLLabelElement:!1,HTMLLegendElement:!1,HTMLLIElement:!1,HTMLLinkElement:!1,HTMLMapElement:!1,HTMLMarqueeElement:!1,HTMLMediaElement:!1,HTMLMenuElement:!1,HTMLMetaElement:!1,HTMLMeterElement:!1,HTMLModElement:!1,HTMLObjectElement:!1,HTMLOListElement:!1,HTMLOptGroupElement:!1,HTMLOptionElement:!1,HTMLOptionsCollection:!1,HTMLOutputElement:!1,HTMLParagraphElement:!1,HTMLParamElement:!1,HTMLPictureElement:!1,HTMLPreElement:!1,HTMLProgressElement:!1,HTMLQuoteElement:!1,HTMLScriptElement:!1,HTMLSelectElement:!1,HTMLShadowElement:!1,HTMLSlotElement:!1,HTMLSourceElement:!1,HTMLSpanElement:!1,HTMLStyleElement:!1,HTMLTableCaptionElement:!1,HTMLTableCellElement:!1,HTMLTableColElement:!1,HTMLTableElement:!1,HTMLTableRowElement:!1,HTMLTableSectionElement:!1,HTMLTemplateElement:!1,HTMLTextAreaElement:!1,HTMLTimeElement:!1,HTMLTitleElement:!1,HTMLTrackElement:!1,HTMLUListElement:!1,HTMLUnknownElement:!1,HTMLVideoElement:!1,IDBCursor:!1,IDBCursorWithValue:!1,IDBDatabase:!1,IDBFactory:!1,IDBIndex:!1,IDBKeyRange:!1,IDBObjectStore:!1,IDBOpenDBRequest:!1,IDBRequest:!1,IDBTransaction:!1,IDBVersionChangeEvent:!1,IdleDeadline:!1,IIRFilterNode:!1,Image:!1,ImageBitmap:!1,ImageBitmapRenderingContext:!1,ImageCapture:!1,ImageData:!1,indexedDB:!1,innerHeight:!1,innerWidth:!1,InputEvent:!1,IntersectionObserver:!1,IntersectionObserverEntry:!1,Intl:!1,isSecureContext:!1,KeyboardEvent:!1,KeyframeEffect:!1,KeyframeEffectReadOnly:!1,length:!1,localStorage:!1,location:!0,Location:!1,locationbar:!1,matchMedia:!1,MediaDeviceInfo:!1,MediaDevices:!1,MediaElementAudioSourceNode:!1,MediaEncryptedEvent:!1,MediaError:!1,MediaKeyMessageEvent:!1,MediaKeySession:!1,MediaKeyStatusMap:!1,MediaKeySystemAccess:!1,MediaList:!1,MediaQueryList:!1,MediaQueryListEvent:!1,MediaRecorder:!1,MediaSettingsRange:!1,MediaSource:!1,MediaStream:!1,MediaStreamAudioDestinationNode:!1,MediaStreamAudioSourceNode:!1,MediaStreamEvent:!1,MediaStreamTrack:!1,MediaStreamTrackEvent:!1,menubar:!1,MessageChannel:!1,MessageEvent:!1,MessagePort:!1,MIDIAccess:!1,MIDIConnectionEvent:!1,MIDIInput:!1,MIDIInputMap:!1,MIDIMessageEvent:!1,MIDIOutput:!1,MIDIOutputMap:!1,MIDIPort:!1,MimeType:!1,MimeTypeArray:!1,MouseEvent:!1,moveBy:!1,moveTo:!1,MutationEvent:!1,MutationObserver:!1,MutationRecord:!1,name:!1,NamedNodeMap:!1,NavigationPreloadManager:!1,navigator:!1,Navigator:!1,NetworkInformation:!1,Node:!1,NodeFilter:!1,NodeIterator:!1,NodeList:!1,Notification:!1,OfflineAudioCompletionEvent:!1,OfflineAudioContext:!1,offscreenBuffering:!1,OffscreenCanvas:!0,onabort:!0,onafterprint:!0,onanimationend:!0,onanimationiteration:!0,onanimationstart:!0,onappinstalled:!0,onauxclick:!0,onbeforeinstallprompt:!0,onbeforeprint:!0,onbeforeunload:!0,onblur:!0,oncancel:!0,oncanplay:!0,oncanplaythrough:!0,onchange:!0,onclick:!0,onclose:!0,oncontextmenu:!0,oncuechange:!0,ondblclick:!0,ondevicemotion:!0,ondeviceorientation:!0,ondeviceorientationabsolute:!0,ondrag:!0,ondragend:!0,ondragenter:!0,ondragleave:!0,ondragover:!0,ondragstart:!0,ondrop:!0,ondurationchange:!0,onemptied:!0,onended:!0,onerror:!0,onfocus:!0,ongotpointercapture:!0,onhashchange:!0,oninput:!0,oninvalid:!0,onkeydown:!0,onkeypress:!0,onkeyup:!0,onlanguagechange:!0,onload:!0,onloadeddata:!0,onloadedmetadata:!0,onloadstart:!0,onlostpointercapture:!0,onmessage:!0,onmessageerror:!0,onmousedown:!0,onmouseenter:!0,onmouseleave:!0,onmousemove:!0,onmouseout:!0,onmouseover:!0,onmouseup:!0,onmousewheel:!0,onoffline:!0,ononline:!0,onpagehide:!0,onpageshow:!0,onpause:!0,onplay:!0,onplaying:!0,onpointercancel:!0,onpointerdown:!0,onpointerenter:!0,onpointerleave:!0,onpointermove:!0,onpointerout:!0,onpointerover:!0,onpointerup:!0,onpopstate:!0,onprogress:!0,onratechange:!0,onrejectionhandled:!0,onreset:!0,onresize:!0,onscroll:!0,onsearch:!0,onseeked:!0,onseeking:!0,onselect:!0,onstalled:!0,onstorage:!0,onsubmit:!0,onsuspend:!0,ontimeupdate:!0,ontoggle:!0,ontransitionend:!0,onunhandledrejection:!0,onunload:!0,onvolumechange:!0,onwaiting:!0,onwheel:!0,open:!1,openDatabase:!1,opener:!1,Option:!1,origin:!1,OscillatorNode:!1,outerHeight:!1,outerWidth:!1,PageTransitionEvent:!1,pageXOffset:!1,pageYOffset:!1,PannerNode:!1,parent:!1,Path2D:!1,PaymentAddress:!1,PaymentRequest:!1,PaymentRequestUpdateEvent:!1,PaymentResponse:!1,performance:!1,Performance:!1,PerformanceEntry:!1,PerformanceLongTaskTiming:!1,PerformanceMark:!1,PerformanceMeasure:!1,PerformanceNavigation:!1,PerformanceNavigationTiming:!1,PerformanceObserver:!1,PerformanceObserverEntryList:!1,PerformancePaintTiming:!1,PerformanceResourceTiming:!1,PerformanceTiming:!1,PeriodicWave:!1,Permissions:!1,PermissionStatus:!1,personalbar:!1,PhotoCapabilities:!1,Plugin:!1,PluginArray:!1,PointerEvent:!1,PopStateEvent:!1,postMessage:!1,Presentation:!1,PresentationAvailability:!1,PresentationConnection:!1,PresentationConnectionAvailableEvent:!1,PresentationConnectionCloseEvent:!1,PresentationConnectionList:!1,PresentationReceiver:!1,PresentationRequest:!1,print:!1,ProcessingInstruction:!1,ProgressEvent:!1,PromiseRejectionEvent:!1,prompt:!1,PushManager:!1,PushSubscription:!1,PushSubscriptionOptions:!1,queueMicrotask:!1,RadioNodeList:!1,Range:!1,ReadableStream:!1,registerProcessor:!1,RemotePlayback:!1,removeEventListener:!1,Request:!1,requestAnimationFrame:!1,requestIdleCallback:!1,resizeBy:!1,ResizeObserver:!1,ResizeObserverEntry:!1,resizeTo:!1,Response:!1,RTCCertificate:!1,RTCDataChannel:!1,RTCDataChannelEvent:!1,RTCDtlsTransport:!1,RTCIceCandidate:!1,RTCIceGatherer:!1,RTCIceTransport:!1,RTCPeerConnection:!1,RTCPeerConnectionIceEvent:!1,RTCRtpContributingSource:!1,RTCRtpReceiver:!1,RTCRtpSender:!1,RTCSctpTransport:!1,RTCSessionDescription:!1,RTCStatsReport:!1,RTCTrackEvent:!1,screen:!1,Screen:!1,screenLeft:!1,ScreenOrientation:!1,screenTop:!1,screenX:!1,screenY:!1,ScriptProcessorNode:!1,scroll:!1,scrollbars:!1,scrollBy:!1,scrollTo:!1,scrollX:!1,scrollY:!1,SecurityPolicyViolationEvent:!1,Selection:!1,self:!1,ServiceWorker:!1,ServiceWorkerContainer:!1,ServiceWorkerRegistration:!1,sessionStorage:!1,setInterval:!1,setTimeout:!1,ShadowRoot:!1,SharedWorker:!1,SourceBuffer:!1,SourceBufferList:!1,speechSynthesis:!1,SpeechSynthesisEvent:!1,SpeechSynthesisUtterance:!1,StaticRange:!1,status:!1,statusbar:!1,StereoPannerNode:!1,stop:!1,Storage:!1,StorageEvent:!1,StorageManager:!1,styleMedia:!1,StyleSheet:!1,StyleSheetList:!1,SubtleCrypto:!1,SVGAElement:!1,SVGAngle:!1,SVGAnimatedAngle:!1,SVGAnimatedBoolean:!1,SVGAnimatedEnumeration:!1,SVGAnimatedInteger:!1,SVGAnimatedLength:!1,SVGAnimatedLengthList:!1,SVGAnimatedNumber:!1,SVGAnimatedNumberList:!1,SVGAnimatedPreserveAspectRatio:!1,SVGAnimatedRect:!1,SVGAnimatedString:!1,SVGAnimatedTransformList:!1,SVGAnimateElement:!1,SVGAnimateMotionElement:!1,SVGAnimateTransformElement:!1,SVGAnimationElement:!1,SVGCircleElement:!1,SVGClipPathElement:!1,SVGComponentTransferFunctionElement:!1,SVGDefsElement:!1,SVGDescElement:!1,SVGDiscardElement:!1,SVGElement:!1,SVGEllipseElement:!1,SVGFEBlendElement:!1,SVGFEColorMatrixElement:!1,SVGFEComponentTransferElement:!1,SVGFECompositeElement:!1,SVGFEConvolveMatrixElement:!1,SVGFEDiffuseLightingElement:!1,SVGFEDisplacementMapElement:!1,SVGFEDistantLightElement:!1,SVGFEDropShadowElement:!1,SVGFEFloodElement:!1,SVGFEFuncAElement:!1,SVGFEFuncBElement:!1,SVGFEFuncGElement:!1,SVGFEFuncRElement:!1,SVGFEGaussianBlurElement:!1,SVGFEImageElement:!1,SVGFEMergeElement:!1,SVGFEMergeNodeElement:!1,SVGFEMorphologyElement:!1,SVGFEOffsetElement:!1,SVGFEPointLightElement:!1,SVGFESpecularLightingElement:!1,SVGFESpotLightElement:!1,SVGFETileElement:!1,SVGFETurbulenceElement:!1,SVGFilterElement:!1,SVGForeignObjectElement:!1,SVGGElement:!1,SVGGeometryElement:!1,SVGGradientElement:!1,SVGGraphicsElement:!1,SVGImageElement:!1,SVGLength:!1,SVGLengthList:!1,SVGLinearGradientElement:!1,SVGLineElement:!1,SVGMarkerElement:!1,SVGMaskElement:!1,SVGMatrix:!1,SVGMetadataElement:!1,SVGMPathElement:!1,SVGNumber:!1,SVGNumberList:!1,SVGPathElement:!1,SVGPatternElement:!1,SVGPoint:!1,SVGPointList:!1,SVGPolygonElement:!1,SVGPolylineElement:!1,SVGPreserveAspectRatio:!1,SVGRadialGradientElement:!1,SVGRect:!1,SVGRectElement:!1,SVGScriptElement:!1,SVGSetElement:!1,SVGStopElement:!1,SVGStringList:!1,SVGStyleElement:!1,SVGSVGElement:!1,SVGSwitchElement:!1,SVGSymbolElement:!1,SVGTextContentElement:!1,SVGTextElement:!1,SVGTextPathElement:!1,SVGTextPositioningElement:!1,SVGTitleElement:!1,SVGTransform:!1,SVGTransformList:!1,SVGTSpanElement:!1,SVGUnitTypes:!1,SVGUseElement:!1,SVGViewElement:!1,TaskAttributionTiming:!1,Text:!1,TextDecoder:!1,TextEncoder:!1,TextEvent:!1,TextMetrics:!1,TextTrack:!1,TextTrackCue:!1,TextTrackCueList:!1,TextTrackList:!1,TimeRanges:!1,toolbar:!1,top:!1,Touch:!1,TouchEvent:!1,TouchList:!1,TrackEvent:!1,TransitionEvent:!1,TreeWalker:!1,UIEvent:!1,URL:!1,URLSearchParams:!1,ValidityState:!1,visualViewport:!1,VisualViewport:!1,VTTCue:!1,WaveShaperNode:!1,WebAssembly:!1,WebGL2RenderingContext:!1,WebGLActiveInfo:!1,WebGLBuffer:!1,WebGLContextEvent:!1,WebGLFramebuffer:!1,WebGLProgram:!1,WebGLQuery:!1,WebGLRenderbuffer:!1,WebGLRenderingContext:!1,WebGLSampler:!1,WebGLShader:!1,WebGLShaderPrecisionFormat:!1,WebGLSync:!1,WebGLTexture:!1,WebGLTransformFeedback:!1,WebGLUniformLocation:!1,WebGLVertexArrayObject:!1,WebSocket:!1,WheelEvent:!1,window:!1,Window:!1,Worker:!1,WritableStream:!1,XMLDocument:!1,XMLHttpRequest:!1,XMLHttpRequestEventTarget:!1,XMLHttpRequestUpload:!1,XMLSerializer:!1,XPathEvaluator:!1,XPathExpression:!1,XPathResult:!1,XSLTProcessor:!1},worker:{addEventListener:!1,applicationCache:!1,atob:!1,Blob:!1,BroadcastChannel:!1,btoa:!1,Cache:!1,caches:!1,clearInterval:!1,clearTimeout:!1,close:!0,console:!1,fetch:!1,FileReaderSync:!1,FormData:!1,Headers:!1,IDBCursor:!1,IDBCursorWithValue:!1,IDBDatabase:!1,IDBFactory:!1,IDBIndex:!1,IDBKeyRange:!1,IDBObjectStore:!1,IDBOpenDBRequest:!1,IDBRequest:!1,IDBTransaction:!1,IDBVersionChangeEvent:!1,ImageData:!1,importScripts:!0,indexedDB:!1,location:!1,MessageChannel:!1,MessagePort:!1,name:!1,navigator:!1,Notification:!1,onclose:!0,onconnect:!0,onerror:!0,onlanguagechange:!0,onmessage:!0,onoffline:!0,ononline:!0,onrejectionhandled:!0,onunhandledrejection:!0,performance:!1,Performance:!1,PerformanceEntry:!1,PerformanceMark:!1,PerformanceMeasure:!1,PerformanceNavigation:!1,PerformanceResourceTiming:!1,PerformanceTiming:!1,postMessage:!0,Promise:!1,queueMicrotask:!1,removeEventListener:!1,Request:!1,Response:!1,self:!0,ServiceWorkerRegistration:!1,setInterval:!1,setTimeout:!1,TextDecoder:!1,TextEncoder:!1,URL:!1,URLSearchParams:!1,WebSocket:!1,Worker:!1,WorkerGlobalScope:!1,XMLHttpRequest:!1},node:{__dirname:!1,__filename:!1,Buffer:!1,clearImmediate:!1,clearInterval:!1,clearTimeout:!1,console:!1,exports:!0,global:!1,Intl:!1,module:!1,process:!1,queueMicrotask:!1,require:!1,setImmediate:!1,setInterval:!1,setTimeout:!1,TextDecoder:!1,TextEncoder:!1,URL:!1,URLSearchParams:!1},commonjs:{exports:!0,global:!1,module:!1,require:!1},amd:{define:!1,require:!1},mocha:{after:!1,afterEach:!1,before:!1,beforeEach:!1,context:!1,describe:!1,it:!1,mocha:!1,run:!1,setup:!1,specify:!1,suite:!1,suiteSetup:!1,suiteTeardown:!1,teardown:!1,test:!1,xcontext:!1,xdescribe:!1,xit:!1,xspecify:!1},jasmine:{afterAll:!1,afterEach:!1,beforeAll:!1,beforeEach:!1,describe:!1,expect:!1,fail:!1,fdescribe:!1,fit:!1,it:!1,jasmine:!1,pending:!1,runs:!1,spyOn:!1,spyOnProperty:!1,waits:!1,waitsFor:!1,xdescribe:!1,xit:!1},jest:{afterAll:!1,afterEach:!1,beforeAll:!1,beforeEach:!1,describe:!1,expect:!1,fdescribe:!1,fit:!1,it:!1,jest:!1,pit:!1,require:!1,test:!1,xdescribe:!1,xit:!1,xtest:!1},qunit:{asyncTest:!1,deepEqual:!1,equal:!1,expect:!1,module:!1,notDeepEqual:!1,notEqual:!1,notOk:!1,notPropEqual:!1,notStrictEqual:!1,ok:!1,propEqual:!1,QUnit:!1,raises:!1,start:!1,stop:!1,strictEqual:!1,test:!1,throws:!1},phantomjs:{console:!0,exports:!0,phantom:!0,require:!0,WebPage:!0},couch:{emit:!1,exports:!1,getRow:!1,log:!1,module:!1,provides:!1,require:!1,respond:!1,send:!1,start:!1,sum:!1},rhino:{defineClass:!1,deserialize:!1,gc:!1,help:!1,importClass:!1,importPackage:!1,java:!1,load:!1,loadClass:!1,Packages:!1,print:!1,quit:!1,readFile:!1,readUrl:!1,runCommand:!1,seal:!1,serialize:!1,spawn:!1,sync:!1,toint32:!1,version:!1},nashorn:{__DIR__:!1,__FILE__:!1,__LINE__:!1,com:!1,edu:!1,exit:!1,java:!1,Java:!1,javafx:!1,JavaImporter:!1,javax:!1,JSAdapter:!1,load:!1,loadWithNewGlobal:!1,org:!1,Packages:!1,print:!1,quit:!1},wsh:{ActiveXObject:!0,Enumerator:!0,GetObject:!0,ScriptEngine:!0,ScriptEngineBuildVersion:!0,ScriptEngineMajorVersion:!0,ScriptEngineMinorVersion:!0,VBArray:!0,WScript:!0,WSH:!0,XDomainRequest:!0},jquery:{$:!1,jQuery:!1},yui:{YAHOO:!1,YAHOO_config:!1,YUI:!1,YUI_config:!1},shelljs:{cat:!1,cd:!1,chmod:!1,config:!1,cp:!1,dirs:!1,echo:!1,env:!1,error:!1,exec:!1,exit:!1,find:!1,grep:!1,ln:!1,ls:!1,mkdir:!1,mv:!1,popd:!1,pushd:!1,pwd:!1,rm:!1,sed:!1,set:!1,target:!1,tempdir:!1,test:!1,touch:!1,which:!1},prototypejs:{$:!1,$$:!1,$A:!1,$break:!1,$continue:!1,$F:!1,$H:!1,$R:!1,$w:!1,Abstract:!1,Ajax:!1,Autocompleter:!1,Builder:!1,Class:!1,Control:!1,Draggable:!1,Draggables:!1,Droppables:!1,Effect:!1,Element:!1,Enumerable:!1,Event:!1,Field:!1,Form:!1,Hash:!1,Insertion:!1,ObjectRange:!1,PeriodicalExecuter:!1,Position:!1,Prototype:!1,Scriptaculous:!1,Selector:!1,Sortable:!1,SortableObserver:!1,Sound:!1,Template:!1,Toggle:!1,Try:!1},meteor:{_:!1,$:!1,Accounts:!1,AccountsClient:!1,AccountsCommon:!1,AccountsServer:!1,App:!1,Assets:!1,Blaze:!1,check:!1,Cordova:!1,DDP:!1,DDPRateLimiter:!1,DDPServer:!1,Deps:!1,EJSON:!1,Email:!1,HTTP:!1,Log:!1,Match:!1,Meteor:!1,Mongo:!1,MongoInternals:!1,Npm:!1,Package:!1,Plugin:!1,process:!1,Random:!1,ReactiveDict:!1,ReactiveVar:!1,Router:!1,ServiceConfiguration:!1,Session:!1,share:!1,Spacebars:!1,Template:!1,Tinytest:!1,Tracker:!1,UI:!1,Utils:!1,WebApp:!1,WebAppInternals:!1},mongo:{_isWindows:!1,_rand:!1,BulkWriteResult:!1,cat:!1,cd:!1,connect:!1,db:!1,getHostName:!1,getMemInfo:!1,hostname:!1,ISODate:!1,listFiles:!1,load:!1,ls:!1,md5sumFile:!1,mkdir:!1,Mongo:!1,NumberInt:!1,NumberLong:!1,ObjectId:!1,PlanCache:!1,print:!1,printjson:!1,pwd:!1,quit:!1,removeFile:!1,rs:!1,sh:!1,UUID:!1,version:!1,WriteResult:!1},applescript:{$:!1,Application:!1,Automation:!1,console:!1,delay:!1,Library:!1,ObjC:!1,ObjectSpecifier:!1,Path:!1,Progress:!1,Ref:!1},serviceworker:{addEventListener:!1,applicationCache:!1,atob:!1,Blob:!1,BroadcastChannel:!1,btoa:!1,Cache:!1,caches:!1,CacheStorage:!1,clearInterval:!1,clearTimeout:!1,Client:!1,clients:!1,Clients:!1,close:!0,console:!1,ExtendableEvent:!1,ExtendableMessageEvent:!1,fetch:!1,FetchEvent:!1,FileReaderSync:!1,FormData:!1,Headers:!1,IDBCursor:!1,IDBCursorWithValue:!1,IDBDatabase:!1,IDBFactory:!1,IDBIndex:!1,IDBKeyRange:!1,IDBObjectStore:!1,IDBOpenDBRequest:!1,IDBRequest:!1,IDBTransaction:!1,IDBVersionChangeEvent:!1,ImageData:!1,importScripts:!1,indexedDB:!1,location:!1,MessageChannel:!1,MessagePort:!1,name:!1,navigator:!1,Notification:!1,onclose:!0,onconnect:!0,onerror:!0,onfetch:!0,oninstall:!0,onlanguagechange:!0,onmessage:!0,onmessageerror:!0,onnotificationclick:!0,onnotificationclose:!0,onoffline:!0,ononline:!0,onpush:!0,onpushsubscriptionchange:!0,onrejectionhandled:!0,onsync:!0,onunhandledrejection:!0,performance:!1,Performance:!1,PerformanceEntry:!1,PerformanceMark:!1,PerformanceMeasure:!1,PerformanceNavigation:!1,PerformanceResourceTiming:!1,PerformanceTiming:!1,postMessage:!0,Promise:!1,queueMicrotask:!1,registration:!1,removeEventListener:!1,Request:!1,Response:!1,self:!1,ServiceWorker:!1,ServiceWorkerContainer:!1,ServiceWorkerGlobalScope:!1,ServiceWorkerMessageEvent:!1,ServiceWorkerRegistration:!1,setInterval:!1,setTimeout:!1,skipWaiting:!1,TextDecoder:!1,TextEncoder:!1,URL:!1,URLSearchParams:!1,WebSocket:!1,WindowClient:!1,Worker:!1,WorkerGlobalScope:!1,XMLHttpRequest:!1},atomtest:{advanceClock:!1,fakeClearInterval:!1,fakeClearTimeout:!1,fakeSetInterval:!1,fakeSetTimeout:!1,resetTimeouts:!1,waitsForPromise:!1},embertest:{andThen:!1,click:!1,currentPath:!1,currentRouteName:!1,currentURL:!1,fillIn:!1,find:!1,findAll:!1,findWithAssert:!1,keyEvent:!1,pauseTest:!1,resumeTest:!1,triggerEvent:!1,visit:!1,wait:!1},protractor:{$:!1,$$:!1,browser:!1,by:!1,By:!1,DartObject:!1,element:!1,protractor:!1},"shared-node-browser":{clearInterval:!1,clearTimeout:!1,console:!1,setInterval:!1,setTimeout:!1,URL:!1,URLSearchParams:!1},webextensions:{browser:!1,chrome:!1,opr:!1},greasemonkey:{cloneInto:!1,createObjectIn:!1,exportFunction:!1,GM:!1,GM_addStyle:!1,GM_deleteValue:!1,GM_getResourceText:!1,GM_getResourceURL:!1,GM_getValue:!1,GM_info:!1,GM_listValues:!1,GM_log:!1,GM_openInTab:!1,GM_registerMenuCommand:!1,GM_setClipboard:!1,GM_setValue:!1,GM_xmlhttpRequest:!1,unsafeWindow:!1},devtools:{$:!1,$_:!1,$$:!1,$0:!1,$1:!1,$2:!1,$3:!1,$4:!1,$x:!1,chrome:!1,clear:!1,copy:!1,debug:!1,dir:!1,dirxml:!1,getEventListeners:!1,inspect:!1,keys:!1,monitor:!1,monitorEvents:!1,profile:!1,profileEnd:!1,queryObjects:!1,table:!1,undebug:!1,unmonitor:!1,unmonitorEvents:!1,values:!1}};function Wp(){return Up?Fp:(Up=1,Fp=qp)}var Gp=(void Tr.env.BABEL_8_BREAKING,Wp()),Vp=Pa,Hp=Yn,Kp=Hc,zp=gu,Xp=cs,Jp=P,Yp=kt,$p=qt,Qp=le,Zp=pe,ef=fe,tf=ge,rf=ye,af=M,nf=F,sf=ve,of=Mt,df=Lt,cf=Vt,lf=V,uf=Y,pf=Ft,ff=Nt,gf=K,yf=Te,mf=Pe,hf=Ae,vf=re,bf=ae,Rf=se,xf=$t,jf=vs,Ef=fs,wf=eu,Sf=Ns,Tf=Bs,Pf=pt,Af=ft,kf=Q,Cf=gt,_f=je,If=Me,Df=Gt,Of=qc;function Nf(e,t){switch(null==e?void 0:e.type){default:var r;if(sf(e)||Df(e))if((ef(e)||rf(e)||sf(e))&&e.source)Nf(e.source,t);else if((rf(e)||sf(e))&&null!=(r=e.specifiers)&&r.length)for(var a,n=j(e.specifiers);!(a=n()).done;){Nf(a.value,t)}else(tf(e)||rf(e))&&e.declaration&&Nf(e.declaration,t);else cf(e)?Nf(e.local,t):!of(e)||lf(e)||gf(e)||hf(e)||t.push(e.value);break;case"MemberExpression":case"OptionalMemberExpression":case"JSXMemberExpression":Nf(e.object,t),Nf(e.property,t);break;case"Identifier":case"JSXIdentifier":t.push(e.name);break;case"CallExpression":case"OptionalCallExpression":case"NewExpression":Nf(e.callee,t);break;case"ObjectExpression":case"ObjectPattern":for(var s,i=j(e.properties);!(s=i()).done;){Nf(s.value,t)}break;case"SpreadElement":case"RestElement":case"UnaryExpression":case"UpdateExpression":Nf(e.argument,t);break;case"ObjectProperty":case"ObjectMethod":case"ClassProperty":case"ClassMethod":case"ClassPrivateProperty":case"ClassPrivateMethod":Nf(e.key,t);break;case"ThisExpression":t.push("this");break;case"Super":t.push("super");break;case"Import":t.push("import");break;case"DoExpression":t.push("do");break;case"YieldExpression":t.push("yield"),Nf(e.argument,t);break;case"AwaitExpression":t.push("await"),Nf(e.argument,t);break;case"AssignmentExpression":Nf(e.left,t);break;case"VariableDeclarator":case"FunctionExpression":case"FunctionDeclaration":case"ClassExpression":case"ClassDeclaration":case"PrivateName":Nf(e.id,t);break;case"ParenthesizedExpression":Nf(e.expression,t);break;case"MetaProperty":Nf(e.meta,t),Nf(e.property,t);break;case"JSXElement":Nf(e.openingElement,t);break;case"JSXOpeningElement":Nf(e.name,t);break;case"JSXFragment":Nf(e.openingFragment,t);break;case"JSXOpeningFragment":t.push("Fragment");break;case"JSXNamespacedName":Nf(e.namespace,t),Nf(e.name,t)}}var Bf={ForStatement:function(e){var t=e.get("init");if(t.isVar()){var r=e.scope;(r.getFunctionParent()||r.getProgramParent()).registerBinding("var",t)}},Declaration:function(e){e.isBlockScoped()||(e.isImportDeclaration()||e.isExportDeclaration()||(e.scope.getFunctionParent()||e.scope.getProgramParent()).registerDeclaration(e))},ImportDeclaration:function(e){e.scope.getBlockParent().registerDeclaration(e)},ReferencedIdentifier:function(e,t){t.references.push(e)},ForXStatement:function(e,t){var r=e.get("left");if(r.isPattern()||r.isIdentifier())t.constantViolations.push(e);else if(r.isVar()){var a=e.scope;(a.getFunctionParent()||a.getProgramParent()).registerBinding("var",r)}},ExportDeclaration:{exit:function(e){var t=e.node,r=e.scope;if(!ef(t)){var a=t.declaration;if(Zp(a)||af(a)){var n=a.id;if(!n)return;var s=r.getBinding(n.name);null==s||s.reference(e)}else if(Rf(a))for(var i,o=j(a.declarations);!(i=o()).done;)for(var d=i.value,c=0,l=Object.keys(zp(d));c1&&(r+=t),"_"+r},t.generateUidBasedOnNode=function(e,t){var r=[];Nf(e,r);var a=r.join("$");return a=a.replace(/^_/,"")||t||"ref",this.generateUid(a.slice(0,20))},t.generateUidIdentifierBasedOnNode=function(e,t){return Xp(this.generateUidBasedOnNode(e,t))},t.isStatic=function(e){if(vf(e)||yf(e)||Cf(e))return!0;if(nf(e)){var t=this.getBinding(e.name);return t?t.constant:this.hasBinding(e.name)}return!1},t.maybeGenerateMemoised=function(e,t){if(this.isStatic(e))return null;var r=this.generateUidIdentifierBasedOnNode(e);return t?r:(this.push({id:r}),Kp(r))},t.checkBlockScopedCollisions=function(e,t,r,a){if("param"!==t&&("local"!==e.kind&&("let"===t||"let"===e.kind||"const"===e.kind||"module"===e.kind||"param"===e.kind&&"const"===t)))throw this.hub.buildError(a,'Duplicate declaration "'+r+'"',TypeError)},t.rename=function(e,t){var r=this.getBinding(e);r&&(t||(t=this.generateUidIdentifier(e).name),new Mp(r,e,t).rename(arguments[2]))},t._renameFromMap=function(e,t,r,a){e[t]&&(e[r]=a,e[t]=null)},t.dump=function(){var e="-".repeat(60);console.log(e);var t=this;do{console.log("#",t.block.type);for(var r=0,a=Object.keys(t.bindings);r0)&&this.isPure(e.body,t));if(Qp(e)){for(var i,o=j(e.body);!(i=o()).done;){var d=i.value;if(!this.isPure(d,t))return!1}return!0}if(Yp(e))return this.isPure(e.left,t)&&this.isPure(e.right,t);if(Jp(e)||Af(e)){for(var c,l=j(e.elements);!(c=l()).done;){var u=c.value;if(null!==u&&!this.isPure(u,t))return!1}return!0}if(uf(e)||Pf(e)){for(var p,f=j(e.properties);!(p=f()).done;){var g=p.value;if(!this.isPure(g,t))return!1}return!0}if(df(e))return!(e.computed&&!this.isPure(e.key,t))&&!((null==(n=e.decorators)?void 0:n.length)>0);if(pf(e))return!(e.computed&&!this.isPure(e.key,t))&&(!((null==(s=e.decorators)?void 0:s.length)>0)&&!((kf(e)||e.static)&&null!==e.value&&!this.isPure(e.value,t)));if(bf(e))return this.isPure(e.argument,t);if(mf(e))return xf(e.tag,"String.raw")&&!this.hasBinding("String",!0)&&this.isPure(e.quasi,t);if(hf(e)){for(var y,m=j(e.expressions);!(y=m()).done;){var h=y.value;if(!this.isPure(h,t))return!1}return!0}return ff(e)},t.setData=function(e,t){return this.data[e]=t},t.getData=function(e){var t=this;do{var r=t.data[e];if(null!=r)return r}while(t=t.parent)},t.removeData=function(e){var t=this;do{null!=t.data[e]&&(t.data[e]=null)}while(t=t.parent)},t.init=function(){this.inited||(this.inited=!0,this.crawl())},t.crawl=function(){var e=this.path;this.references=Object.create(null),this.bindings=Object.create(null),this.globals=Object.create(null),this.uids=Object.create(null),this.data=Object.create(null);var t=this.getProgramParent();if(!t.crawling){var r={references:[],constantViolations:[],assignments:[]};if(this.crawling=!0,"Program"!==e.type&&rp(Bf)){for(var a,n=j(Bf.enter);!(a=n()).done;){a.value.call(r,e,r)}var s=Bf[e.type];if(s)for(var i,o=j(s.enter);!(i=o()).done;){i.value.call(r,e,r)}}e.traverse(Bf,r),this.crawling=!1;for(var d,c=j(r.assignments);!(d=c()).done;){for(var l=d.value,u=l.getBindingIdentifiers(),p=0,f=Object.keys(u);p>18&63]+Gf[n>>12&63]+Gf[n>>6&63]+Gf[63&n]);return s.join("")}function Jf(e){var t;Kf||zf();for(var r=e.length,a=r%3,n="",s=[],i=16383,o=0,d=r-a;od?d:o+i));return 1===a?(t=e[r-1],n+=Gf[t>>2],n+=Gf[t<<4&63],n+="=="):2===a&&(t=(e[r-2]<<8)+e[r-1],n+=Gf[t>>10],n+=Gf[t>>4&63],n+=Gf[t<<2&63],n+="="),s.push(n),s.join("")}function Yf(e,t,r,a,n){var s,i,o=8*n-a-1,d=(1<>1,l=-7,u=r?n-1:0,p=r?-1:1,f=e[t+u];for(u+=p,s=f&(1<<-l)-1,f>>=-l,l+=o;l>0;s=256*s+e[t+u],u+=p,l-=8);for(i=s&(1<<-l)-1,s>>=-l,l+=a;l>0;i=256*i+e[t+u],u+=p,l-=8);if(0===s)s=1-c;else{if(s===d)return i?NaN:1/0*(f?-1:1);i+=Math.pow(2,a),s-=c}return(f?-1:1)*i*Math.pow(2,s-a)}function $f(e,t,r,a,n,s){var i,o,d,c=8*s-n-1,l=(1<>1,p=23===n?Math.pow(2,-24)-Math.pow(2,-77):0,f=a?0:s-1,g=a?1:-1,y=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(o=isNaN(t)?1:0,i=l):(i=Math.floor(Math.log(t)/Math.LN2),t*(d=Math.pow(2,-i))<1&&(i--,d*=2),(t+=i+u>=1?p/d:p*Math.pow(2,1-u))*d>=2&&(i++,d/=2),i+u>=l?(o=0,i=l):i+u>=1?(o=(t*d-1)*Math.pow(2,n),i+=u):(o=t*Math.pow(2,u-1)*Math.pow(2,n),i=0));n>=8;e[r+f]=255&o,f+=g,o/=256,n-=8);for(i=i<0;e[r+f]=255&i,f+=g,i/=256,c-=8);e[r+f-g]|=128*y}var Qf={}.toString,Zf=Array.isArray||function(e){return"[object Array]"==Qf.call(e)};function eg(){return rg.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function tg(e,t){if(eg()=eg())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+eg().toString(16)+" bytes");return 0|e}function dg(e){return!(null==e||!e._isBuffer)}function cg(e,t){if(dg(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var r=e.length;if(0===r)return 0;for(var a=!1;;)switch(t){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":case void 0:return Bg(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return Mg(e).length;default:if(a)return Bg(e).length;t=(""+t).toLowerCase(),a=!0}}function lg(e,t,r){var a=!1;if((void 0===t||t<0)&&(t=0),t>this.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(t>>>=0))return"";for(e||(e="utf8");;)switch(e){case"hex":return Sg(this,t,r);case"utf8":case"utf-8":return xg(this,t,r);case"ascii":return Eg(this,t,r);case"latin1":case"binary":return wg(this,t,r);case"base64":return Rg(this,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return Tg(this,t,r);default:if(a)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),a=!0}}function ug(e,t,r){var a=e[t];e[t]=e[r],e[r]=a}function pg(e,t,r,a,n){if(0===e.length)return-1;if("string"==typeof r?(a=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),r=+r,isNaN(r)&&(r=n?0:e.length-1),r<0&&(r=e.length+r),r>=e.length){if(n)return-1;r=e.length-1}else if(r<0){if(!n)return-1;r=0}if("string"==typeof t&&(t=rg.from(t,a)),dg(t))return 0===t.length?-1:fg(e,t,r,a,n);if("number"==typeof t)return t&=255,rg.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?n?Uint8Array.prototype.indexOf.call(e,t,r):Uint8Array.prototype.lastIndexOf.call(e,t,r):fg(e,[t],r,a,n);throw new TypeError("val must be string, number or Buffer")}function fg(e,t,r,a,n){var s,i=1,o=e.length,d=t.length;if(void 0!==a&&("ucs2"===(a=String(a).toLowerCase())||"ucs-2"===a||"utf16le"===a||"utf-16le"===a)){if(e.length<2||t.length<2)return-1;i=2,o/=2,d/=2,r/=2}function c(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(n){var l=-1;for(s=r;so&&(r=o-d),s=r;s>=0;s--){for(var u=!0,p=0;pn&&(a=n):a=n;var s=t.length;if(s%2!=0)throw new TypeError("Invalid hex string");a>s/2&&(a=s/2);for(var i=0;i>8,n=r%256,s.push(n),s.push(a);return s}(t,e.length-r),e,r,a)}function Rg(e,t,r){return 0===t&&r===e.length?Jf(e):Jf(e.slice(t,r))}function xg(e,t,r){r=Math.min(e.length,r);for(var a=[],n=t;n239?4:c>223?3:c>191?2:1;if(n+u<=r)switch(u){case 1:c<128&&(l=c);break;case 2:128==(192&(s=e[n+1]))&&(d=(31&c)<<6|63&s)>127&&(l=d);break;case 3:s=e[n+1],i=e[n+2],128==(192&s)&&128==(192&i)&&(d=(15&c)<<12|(63&s)<<6|63&i)>2047&&(d<55296||d>57343)&&(l=d);break;case 4:s=e[n+1],i=e[n+2],o=e[n+3],128==(192&s)&&128==(192&i)&&128==(192&o)&&(d=(15&c)<<18|(63&s)<<12|(63&i)<<6|63&o)>65535&&d<1114112&&(l=d)}null===l?(l=65533,u=1):l>65535&&(l-=65536,a.push(l>>>10&1023|55296),l=56320|1023&l),a.push(l),n+=u}return function(e){var t=e.length;if(t<=jg)return String.fromCharCode.apply(String,e);var r="",a=0;for(;a0&&(e=this.toString("hex",0,50).match(/.{2}/g).join(" "),this.length>50&&(e+=" ... ")),""},rg.prototype.compare=function(e,t,r,a,n){if(!dg(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===r&&(r=e?e.length:0),void 0===a&&(a=0),void 0===n&&(n=this.length),t<0||r>e.length||a<0||n>this.length)throw new RangeError("out of range index");if(a>=n&&t>=r)return 0;if(a>=n)return-1;if(t>=r)return 1;if(this===e)return 0;for(var s=(n>>>=0)-(a>>>=0),i=(r>>>=0)-(t>>>=0),o=Math.min(s,i),d=this.slice(a,n),c=e.slice(t,r),l=0;ln)&&(r=n),e.length>0&&(r<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");a||(a="utf8");for(var s=!1;;)switch(a){case"hex":return gg(this,e,t,r);case"utf8":case"utf-8":return yg(this,e,t,r);case"ascii":return mg(this,e,t,r);case"latin1":case"binary":return hg(this,e,t,r);case"base64":return vg(this,e,t,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return bg(this,e,t,r);default:if(s)throw new TypeError("Unknown encoding: "+a);a=(""+a).toLowerCase(),s=!0}},rg.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var jg=4096;function Eg(e,t,r){var a="";r=Math.min(e.length,r);for(var n=t;na)&&(r=a);for(var n="",s=t;sr)throw new RangeError("Trying to access beyond buffer length")}function Ag(e,t,r,a,n,s){if(!dg(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>n||te.length)throw new RangeError("Index out of range")}function kg(e,t,r,a){t<0&&(t=65535+t+1);for(var n=0,s=Math.min(e.length-r,2);n>>8*(a?n:1-n)}function Cg(e,t,r,a){t<0&&(t=4294967295+t+1);for(var n=0,s=Math.min(e.length-r,4);n>>8*(a?n:3-n)&255}function _g(e,t,r,a,n,s){if(r+a>e.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function Ig(e,t,r,a,n){return n||_g(e,0,r,4),$f(e,t,r,a,23,4),r+4}function Dg(e,t,r,a,n){return n||_g(e,0,r,8),$f(e,t,r,a,52,8),r+8}rg.prototype.slice=function(e,t){var r,a=this.length;if((e=~~e)<0?(e+=a)<0&&(e=0):e>a&&(e=a),(t=void 0===t?a:~~t)<0?(t+=a)<0&&(t=0):t>a&&(t=a),t0&&(n*=256);)a+=this[e+--t]*n;return a},rg.prototype.readUInt8=function(e,t){return t||Pg(e,1,this.length),this[e]},rg.prototype.readUInt16LE=function(e,t){return t||Pg(e,2,this.length),this[e]|this[e+1]<<8},rg.prototype.readUInt16BE=function(e,t){return t||Pg(e,2,this.length),this[e]<<8|this[e+1]},rg.prototype.readUInt32LE=function(e,t){return t||Pg(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},rg.prototype.readUInt32BE=function(e,t){return t||Pg(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},rg.prototype.readIntLE=function(e,t,r){e|=0,t|=0,r||Pg(e,t,this.length);for(var a=this[e],n=1,s=0;++s=(n*=128)&&(a-=Math.pow(2,8*t)),a},rg.prototype.readIntBE=function(e,t,r){e|=0,t|=0,r||Pg(e,t,this.length);for(var a=t,n=1,s=this[e+--a];a>0&&(n*=256);)s+=this[e+--a]*n;return s>=(n*=128)&&(s-=Math.pow(2,8*t)),s},rg.prototype.readInt8=function(e,t){return t||Pg(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},rg.prototype.readInt16LE=function(e,t){t||Pg(e,2,this.length);var r=this[e]|this[e+1]<<8;return 32768&r?4294901760|r:r},rg.prototype.readInt16BE=function(e,t){t||Pg(e,2,this.length);var r=this[e+1]|this[e]<<8;return 32768&r?4294901760|r:r},rg.prototype.readInt32LE=function(e,t){return t||Pg(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},rg.prototype.readInt32BE=function(e,t){return t||Pg(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},rg.prototype.readFloatLE=function(e,t){return t||Pg(e,4,this.length),Yf(this,e,!0,23,4)},rg.prototype.readFloatBE=function(e,t){return t||Pg(e,4,this.length),Yf(this,e,!1,23,4)},rg.prototype.readDoubleLE=function(e,t){return t||Pg(e,8,this.length),Yf(this,e,!0,52,8)},rg.prototype.readDoubleBE=function(e,t){return t||Pg(e,8,this.length),Yf(this,e,!1,52,8)},rg.prototype.writeUIntLE=function(e,t,r,a){(e=+e,t|=0,r|=0,a)||Ag(this,e,t,r,Math.pow(2,8*r)-1,0);var n=1,s=0;for(this[t]=255&e;++s=0&&(s*=256);)this[t+n]=e/s&255;return t+r},rg.prototype.writeUInt8=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,1,255,0),rg.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},rg.prototype.writeUInt16LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,65535,0),rg.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):kg(this,e,t,!0),t+2},rg.prototype.writeUInt16BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,65535,0),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):kg(this,e,t,!1),t+2},rg.prototype.writeUInt32LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,4294967295,0),rg.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):Cg(this,e,t,!0),t+4},rg.prototype.writeUInt32BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,4294967295,0),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):Cg(this,e,t,!1),t+4},rg.prototype.writeIntLE=function(e,t,r,a){if(e=+e,t|=0,!a){var n=Math.pow(2,8*r-1);Ag(this,e,t,r,n-1,-n)}var s=0,i=1,o=0;for(this[t]=255&e;++s>0)-o&255;return t+r},rg.prototype.writeIntBE=function(e,t,r,a){if(e=+e,t|=0,!a){var n=Math.pow(2,8*r-1);Ag(this,e,t,r,n-1,-n)}var s=r-1,i=1,o=0;for(this[t+s]=255&e;--s>=0&&(i*=256);)e<0&&0===o&&0!==this[t+s+1]&&(o=1),this[t+s]=(e/i>>0)-o&255;return t+r},rg.prototype.writeInt8=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,1,127,-128),rg.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),e<0&&(e=255+e+1),this[t]=255&e,t+1},rg.prototype.writeInt16LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,32767,-32768),rg.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):kg(this,e,t,!0),t+2},rg.prototype.writeInt16BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,2,32767,-32768),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):kg(this,e,t,!1),t+2},rg.prototype.writeInt32LE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,2147483647,-2147483648),rg.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):Cg(this,e,t,!0),t+4},rg.prototype.writeInt32BE=function(e,t,r){return e=+e,t|=0,r||Ag(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),rg.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):Cg(this,e,t,!1),t+4},rg.prototype.writeFloatLE=function(e,t,r){return Ig(this,e,t,!0,r)},rg.prototype.writeFloatBE=function(e,t,r){return Ig(this,e,t,!1,r)},rg.prototype.writeDoubleLE=function(e,t,r){return Dg(this,e,t,!0,r)},rg.prototype.writeDoubleBE=function(e,t,r){return Dg(this,e,t,!1,r)},rg.prototype.copy=function(e,t,r,a){if(r||(r=0),a||0===a||(a=this.length),t>=e.length&&(t=e.length),t||(t=0),a>0&&a=this.length)throw new RangeError("sourceStart out of bounds");if(a<0)throw new RangeError("sourceEnd out of bounds");a>this.length&&(a=this.length),e.length-t=0;--n)e[n+t]=this[n+r];else if(s<1e3||!rg.TYPED_ARRAY_SUPPORT)for(n=0;n>>=0,r=void 0===r?this.length:r>>>0,e||(e=0),"number"==typeof e)for(s=t;s55295&&r<57344){if(!n){if(r>56319){(t-=3)>-1&&s.push(239,191,189);continue}if(i+1===a){(t-=3)>-1&&s.push(239,191,189);continue}n=r;continue}if(r<56320){(t-=3)>-1&&s.push(239,191,189),n=r;continue}r=65536+(n-55296<<10|r-56320)}else n&&(t-=3)>-1&&s.push(239,191,189);if(n=null,r<128){if((t-=1)<0)break;s.push(r)}else if(r<2048){if((t-=2)<0)break;s.push(r>>6|192,63&r|128)}else if(r<65536){if((t-=3)<0)break;s.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;s.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return s}function Mg(e){return function(e){var t,r,a,n,s,i;Kf||zf();var o=e.length;if(o%4>0)throw new Error("Invalid string. Length must be a multiple of 4");s="="===e[o-2]?2:"="===e[o-1]?1:0,i=new Hf(3*o/4-s),a=s>0?o-4:o;var d=0;for(t=0,r=0;t>16&255,i[d++]=n>>8&255,i[d++]=255&n;return 2===s?(n=Vf[e.charCodeAt(t)]<<2|Vf[e.charCodeAt(t+1)]>>4,i[d++]=255&n):1===s&&(n=Vf[e.charCodeAt(t)]<<10|Vf[e.charCodeAt(t+1)]<<4|Vf[e.charCodeAt(t+2)]>>2,i[d++]=n>>8&255,i[d++]=255&n),i}(function(e){if((e=function(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}(e).replace(Og,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function Lg(e,t,r,a){for(var n=0;n=t.length||n>=e.length);++n)t[n+r]=e[n];return n}function Fg(e){return null!=e&&(!!e._isBuffer||Ug(e)||function(e){return"function"==typeof e.readFloatLE&&"function"==typeof e.slice&&Ug(e.slice(0,0))}(e))}function Ug(e){return!!e.constructor&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}var qg,Wg={exports:{}};function Gg(){return qg||(qg=1,function(e,t){!function(e){for(var t=",".charCodeAt(0),r=";".charCodeAt(0),a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(64),s=new Uint8Array(128),i=0;i>>=1,c&&(n=-2147483648|-n),r[a]+=n,t}function p(e,r,a){return!(r>=a)&&e.charCodeAt(r)!==t}function f(e){e.sort(g)}function g(e,t){return e[0]-t[0]}function y(e){for(var a=new Int32Array(5),n=16384,s=n-36,i=new Uint8Array(n),o=i.subarray(0,s),c=0,l="",u=0;u0&&(c===n&&(l+=d.decode(i),c=0),i[c++]=r),0!==p.length){a[0]=0;for(var f=0;fs&&(l+=d.decode(o),i.copyWithin(0,s,c),c-=s),f>0&&(i[c++]=t),c=m(i,c,a,g,0),1!==g.length&&(c=m(i,c,a,g,1),c=m(i,c,a,g,2),c=m(i,c,a,g,3),4!==g.length&&(c=m(i,c,a,g,4)))}}}return l+d.decode(i.subarray(0,c))}function m(e,t,r,a,s){var i=a[s],o=i-r[s];r[s]=i,o=o<0?-o<<1|1:o<<1;do{var d=31&o;(o>>>=5)>0&&(d|=32),e[t++]=n[d]}while(o>0);return t}e.decode=c,e.encode=y,Object.defineProperty(e,"__esModule",{value:!0})}(t)}(0,Wg.exports)),Wg.exports}var Vg,Hg={exports:{}},Kg={exports:{}};function zg(){return Vg||(Vg=1,function(e,t){!function(e){for(var t=",".charCodeAt(0),r=";".charCodeAt(0),a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(64),s=new Uint8Array(128),i=0;i>>=1,c&&(n=-2147483648|-n),r[a]+=n,t}function p(e,r,a){return!(r>=a)&&e.charCodeAt(r)!==t}function f(e){e.sort(g)}function g(e,t){return e[0]-t[0]}function y(e){for(var a=new Int32Array(5),n=16384,s=n-36,i=new Uint8Array(n),o=i.subarray(0,s),c=0,l="",u=0;u0&&(c===n&&(l+=d.decode(i),c=0),i[c++]=r),0!==p.length){a[0]=0;for(var f=0;fs&&(l+=d.decode(o),i.copyWithin(0,s,c),c-=s),f>0&&(i[c++]=t),c=m(i,c,a,g,0),1!==g.length&&(c=m(i,c,a,g,1),c=m(i,c,a,g,2),c=m(i,c,a,g,3),4!==g.length&&(c=m(i,c,a,g,4)))}}}return l+d.decode(i.subarray(0,c))}function m(e,t,r,a,s){var i=a[s],o=i-r[s];r[s]=i,o=o<0?-o<<1|1:o<<1;do{var d=31&o;(o>>>=5)>0&&(d|=32),e[t++]=n[d]}while(o>0);return t}e.decode=c,e.encode=y,Object.defineProperty(e,"__esModule",{value:!0})}(t)}(0,Kg.exports)),Kg.exports}var Xg,Jg={exports:{}};function Yg(){return Xg||(Xg=1,function(e,t){e.exports=function(){var e,t=/^[\w+.-]+:\/\//,r=/^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/,a=/^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i;function n(e){return t.test(e)}function s(e){return e.startsWith("//")}function i(e){return e.startsWith("/")}function o(e){return e.startsWith("file:")}function d(e){return/^[.?#]/.test(e)}function c(e){var t=r.exec(e);return u(t[1],t[2]||"",t[3],t[4]||"",t[5]||"/",t[6]||"",t[7]||"")}function l(e){var t=a.exec(e),r=t[2];return u("file:","",t[1]||"","",i(r)?r:"/"+r,t[3]||"",t[4]||"")}function u(t,r,a,n,s,i,o){return{scheme:t,user:r,host:a,port:n,path:s,query:i,hash:o,type:e.Absolute}}function p(t){if(s(t)){var r=c("http:"+t);return r.scheme="",r.type=e.SchemeRelative,r}if(i(t)){var a=c("http://foo.com"+t);return a.scheme="",a.host="",a.type=e.AbsolutePath,a}if(o(t))return l(t);if(n(t))return c(t);var d=c("http://foo.com/"+t);return d.scheme="",d.host="",d.type=t?t.startsWith("?")?e.Query:t.startsWith("#")?e.Hash:e.RelativePath:e.Empty,d}function f(e){if(e.endsWith("/.."))return e;var t=e.lastIndexOf("/");return e.slice(0,t+1)}function g(e,t){y(t,t.type),"/"===e.path?e.path=t.path:e.path=f(t.path)+e.path}function y(t,r){for(var a=r<=e.RelativePath,n=t.path.split("/"),s=1,i=0,o=!1,d=1;dn&&(n=i)}y(a,n);var o=a.query+a.hash;switch(n){case e.Hash:case e.Query:return o;case e.RelativePath:var c=a.path.slice(1);return c?d(r||t)&&!d(c)?"./"+c+o:c+o:o||".";case e.AbsolutePath:return a.path+o;default:return a.scheme+"//"+a.user+a.host+a.port+a.path+o}}return function(e){e[e.Empty=1]="Empty",e[e.Hash=2]="Hash",e[e.Query=3]="Query",e[e.RelativePath=4]="RelativePath",e[e.AbsolutePath=5]="AbsolutePath",e[e.SchemeRelative=6]="SchemeRelative",e[e.Absolute=7]="Absolute"}(e||(e={})),m}()}(Jg)),Jg.exports}!function(e,t){!function(e,t,r){function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=a(r);function s(e,t){return t&&!t.endsWith("/")&&(t+="/"),n.default(e,t)}function i(e){if(!e)return"";var t=e.lastIndexOf("/");return e.slice(0,t+1)}var o=0,c=1,l=2,u=3,p=4,f=1,g=2;function y(e,t){var r=m(e,0);if(r===e.length)return e;t||(e=e.slice());for(var a=r;a>1),s=e[n][o]-t;if(0===s)return R=!0,n;s<0?r=n+1:a=n-1}return R=!1,r-1}function j(e,t,r){for(var a=r+1;a=0&&e[a][o]===t;r=a--);return r}function w(){return{lastKey:-1,lastNeedle:-1,lastIndex:-1}}function S(e,t,r,a){var n=r.lastKey,s=r.lastNeedle,i=r.lastIndex,d=0,c=e.length-1;if(a===n){if(t===s)return R=-1!==i&&e[i][o]===t,i;t>=s?d=-1===i?0:i:c=i}return r.lastKey=a,r.lastNeedle=t,r.lastIndex=x(e,t,d,c)}function T(e,t){for(var r=t.map(A),a=0;at;a--)e[a]=e[a-1];e[t]=r}function A(){return{__proto__:null}}var k=function(t,r){var a="string"==typeof t?JSON.parse(t):t;if(!("sections"in a))return new L(a,r);var n=[],s=[],i=[],o=[];C(a,r,n,s,i,o,0,0,1/0,1/0);var d={version:3,file:a.file,names:o,sources:s,sourcesContent:i,mappings:n};return e.presortedDecodedMap(d)};function C(e,t,r,a,n,s,i,o,d,c){for(var l=e.sections,u=0;ug)return;for(var S=D(a,w),T=0===E?f:0,P=b[E],A=0;A=y)return;if(1!==k.length){var O=h+k[c],N=k[l],B=k[u];S.push(4===k.length?[_,O,N,B]:[_,O,N,B,v+k[p]])}else S.push([_])}}}function I(e,t){for(var r=0;r=n.length)return null;var s=n[r],i=W(s,t._decodedMemo,r,a,M);return-1===i?null:s[i]},e.originalPositionFor=function(t,r){var a=r.line,n=r.column,s=r.bias;if(--a<0)throw new Error(O);if(n<0)throw new Error(N);var i=e.decodedMappings(t);if(a>=i.length)return U(null,null,null,null);var o=i[a],d=W(o,t._decodedMemo,a,n,s||M);if(-1===d)return U(null,null,null,null);var f=o[d];if(1===f.length)return U(null,null,null,null);var g=t.names;return U(t.resolvedSources[f[c]],f[l]+1,f[u],5===f.length?g[f[p]]:null)},e.allGeneratedPositionsFor=function(e,t){return r(e,t.source,t.line,t.column,t.bias||B,!0)},e.generatedPositionFor=function(e,t){return r(e,t.source,t.line,t.column,t.bias||M,!1)},e.eachMapping=function(t,r){for(var a=e.decodedMappings(t),n=t.names,s=t.resolvedSources,i=0;i=0&&!(t>=e[a][s]);r=a--);return r}function y(e,t,r){for(var a=e.length;a>t;a--)e[a]=e[a-1];e[t]=r}function m(e){for(var t=e.length,r=t,a=r-1;a>=0&&!(e[a].length>0);r=a,a--);r1?this._indentChar.repeat(t):this._indentChar}else this._str+=t>1?String.fromCharCode(e).repeat(t):String.fromCharCode(e);10!==e?(this._mark(r.line,r.column,r.identifierName,r.identifierNamePos,r.filename),this._position.column+=t):(this._position.line++,this._position.column=0),this._canMarkIdName&&(r.identifierName=void 0,r.identifierNamePos=void 0)},t._append=function(e,t,r){var a=e.length,n=this._position;if(this._last=e.charCodeAt(a-1),++this._appendCount>4096?(this._str,this._buf+=this._str,this._str=e,this._appendCount=0):this._str+=e,r||this._map){var s=t.column,i=t.identifierName,o=t.identifierNamePos,d=t.filename,c=t.line;null==i&&null==o||!this._canMarkIdName||(t.identifierName=void 0,t.identifierNamePos=void 0);var l=e.indexOf("\n"),u=0;for(0!==l&&this._mark(c,s,i,o,d);-1!==l;)n.line++,n.column=0,(u=l+1)=0&&10===this._queue[r].char;r--)t++;return t===e&&10===this._last?t+1:t},t.endsWithCharAndNewline=function(){var e=this._queue,t=this._queueCursor;if(0!==t){if(10!==e[t-1].char)return;return t>1?e[t-2].char:this._last}},t.hasContent=function(){return 0!==this._queueCursor||!!this._last},t.exactSource=function(e,t){if(this._map){this.source("start",e);var r=e.identifierName,a=this._sourcePosition;r&&(this._canMarkIdName=!1,a.identifierName=r),t(),r&&(this._canMarkIdName=!0,a.identifierName=void 0,a.identifierNamePos=void 0),this.source("end",e)}else t()},t.source=function(e,t){this._map&&this._normalizePosition(e,t,0)},t.sourceWithOffset=function(e,t,r){this._map&&this._normalizePosition(e,t,r)},t.withSource=function(e,t,r){this._map&&this.source(e,t),r()},t._normalizePosition=function(e,t,r){var a=t[e],n=this._sourcePosition;a&&(n.line=a.line,n.column=Math.max(a.column+r,0),n.filename=t.filename)},t.getCurrentColumn=function(){for(var e=this._queue,t=this._queueCursor,r=-1,a=0,n=0;n",0],["&&",1],["|",2],["^",3],["&",4],["==",5],["===",5],["!=",5],["!==",5],["<",6],[">",6],["<=",6],[">=",6],["in",6],["instanceof",6],[">>",7],["<<",7],[">>>",7],["+",8],["-",8],["*",9],["/",9],["%",9],["**",10]]);function Iy(e){return"TSAsExpression"===e||"TSSatisfiesExpression"===e||"TSTypeAssertion"===e}var Dy=function(e,t){var r=t.type;return("ClassDeclaration"===r||"ClassExpression"===r)&&t.superClass===e},Oy=function(e,t){var r=t.type;return("MemberExpression"===r||"OptionalMemberExpression"===r)&&t.object===e||("CallExpression"===r||"OptionalCallExpression"===r||"NewExpression"===r)&&t.callee===e||"TaggedTemplateExpression"===r&&t.tag===e||"TSNonNullExpression"===r};function Ny(e,t){var r=t.type;return"ArrayTypeAnnotation"===r||"NullableTypeAnnotation"===r||"IntersectionTypeAnnotation"===r||"UnionTypeAnnotation"===r}function By(){return!0}function My(e,t){var r=t.type;return"TSArrayType"===r||"TSOptionalType"===r||"TSIntersectionType"===r||"TSUnionType"===r||"TSRestType"===r}function Ly(e,t){var r=t.type;return"BinaryExpression"===r||"LogicalExpression"===r||"UnaryExpression"===r||"SpreadElement"===r||Oy(e,t)||"AwaitExpression"===r&&Cy(e)||"ConditionalExpression"===r&&e===t.test||Dy(e,t)}function Fy(e,t){return Oy(e,t)||jy(t)&&"**"===t.operator&&t.left===e||Dy(e,t)}function Uy(e,t){var r=t.type;return!!("UnaryExpression"===r||"SpreadElement"===r||"BinaryExpression"===r||"LogicalExpression"===r||"ConditionalExpression"===r&&t.test===e||"AwaitExpression"===r||Iy(r))||Fy(e,t)}function qy(e,t){return Ey(t)&&t.callee===e||Py(t)&&t.object===e}function Wy(e,t){var r=1&t,a=2&t,n=4&t,s=8&t,i=16&t,o=32&t,d=e.length-1;if(!(d<=0)){for(var c=e[d],l=e[--d];d>=0;){var u=l.type;if(r&&"ExpressionStatement"===u&&l.expression===c||n&&"ExportDefaultDeclaration"===u&&c===l.declaration||a&&"ArrowFunctionExpression"===u&&l.body===c||s&&"ForStatement"===u&&l.init===c||i&&"ForInStatement"===u&&l.left===c||o&&"ForOfStatement"===u&&l.left===c)return!0;if(!(d>0&&(Oy(c,l)&&"NewExpression"!==u||"SequenceExpression"===u&&l.expressions[0]===c||"UpdateExpression"===u&&!l.prefix||"ConditionalExpression"===u&&l.test===c||("BinaryExpression"===u||"LogicalExpression"===u)&&l.left===c||"AssignmentExpression"===u&&l.left===c)))return!1;c=l,l=e[--d]}return!1}}var Gy=Object.freeze({__proto__:null,ArrowFunctionExpression:function(e,t){return wy(t)||Uy(e,t)},AssignmentExpression:function(e,t){return!!Ay(e.left)||Uy(e,t)},AwaitExpression:Ly,Binary:function(e,t){var r=t.type;if("**"===e.operator&&"BinaryExpression"===r&&"**"===t.operator)return t.left===e;if(Dy(e,t))return!0;if(Oy(e,t)||"UnaryExpression"===r||"SpreadElement"===r||"AwaitExpression"===r)return!0;if("BinaryExpression"===r||"LogicalExpression"===r){var a=_y.get(t.operator),n=_y.get(e.operator);if(a===n&&t.right===e&&"LogicalExpression"!==r||a>n)return!0}},BinaryExpression:function(e,t){if("in"===e.operator){var r=t.type;return"VariableDeclarator"===r||"ForStatement"===r||"ForInStatement"===r||"ForOfStatement"===r}return!1},ClassExpression:function(e,t,r){return Wy(r,5)},ConditionalExpression:Uy,DoExpression:function(e,t,r){return!e.async&&Wy(r,1)},FunctionExpression:function(e,t,r){return Wy(r,5)},FunctionTypeAnnotation:function(e,t,r){if(!(r.length<3)){var a=t.type;return"UnionTypeAnnotation"===a||"IntersectionTypeAnnotation"===a||"ArrayTypeAnnotation"===a||"TypeAnnotation"===a&&xy(r[r.length-3])}},Identifier:function(e,t,r){var a,n=t.type;if(null!=(a=e.extra)&&a.parenthesized&&"AssignmentExpression"===n&&t.left===e){var s=t.right.type;if(("FunctionExpression"===s||"ClassExpression"===s)&&null==t.right.id)return!0}return"let"===e.name?Wy(r,Py(t,{object:e,computed:!0})||ky(t,{object:e,computed:!0,optional:!1})?57:32):"async"===e.name&&Sy(t)&&e===t.left},IntersectionTypeAnnotation:Ny,LogicalExpression:function(e,t){var r=t.type;if(Iy(r))return!0;if("LogicalExpression"!==r)return!1;switch(e.operator){case"||":return"??"===t.operator||"&&"===t.operator;case"&&":return"??"===t.operator;case"??":return"??"!==t.operator}},NullableTypeAnnotation:function(e,t){return Ry(t)},ObjectExpression:function(e,t,r){return Wy(r,3)},OptionalCallExpression:qy,OptionalIndexedAccessType:function(e,t){return Ty(t)&&t.objectType===e},OptionalMemberExpression:qy,SequenceExpression:function(e,t){var r=t.type;return!("ForStatement"===r||"ThrowStatement"===r||"ReturnStatement"===r||"IfStatement"===r&&t.test===e||"WhileStatement"===r&&t.test===e||"ForInStatement"===r&&t.right===e||"SwitchStatement"===r&&t.discriminant===e||"ExpressionStatement"===r&&t.expression===e)},TSAsExpression:By,TSInferType:function(e,t){var r=t.type;return"TSArrayType"===r||"TSOptionalType"===r},TSInstantiationExpression:function(e,t){var r=t.type;return("CallExpression"===r||"OptionalCallExpression"===r||"NewExpression"===r||"TSInstantiationExpression"===r)&&!!t.typeParameters},TSIntersectionType:My,TSSatisfiesExpression:By,TSTypeAssertion:By,TSUnionType:My,UnaryLike:Fy,UnionTypeAnnotation:Ny,UpdateExpression:function(e,t){return Oy(e,t)||Dy(e,t)},YieldExpression:Ly}),Vy=Ca,Hy=_,Ky=z,zy=X;function Xy(e){var t=new Map;function r(e,r){var a=t.get(e);t.set(e,a?function(e,t,n){var s;return null!=(s=a(e,t,n))?s:r(e,t,n)}:r)}for(var a=0,n=Object.keys(e);a=55296&&N<=56319&&D>I+1){var B=_.charCodeAt(I+1);if(B>=56320&&B<=57343){var M=(1024*(N-55296)+B-56320+65536).toString(16);b||(M=M.toUpperCase()),m+="\\u{"+M+"}",++I;continue}}}if(!l.escapeEverything){if(c.test(O)){m+=O;continue}if('"'==O){m+=h==O?'\\"':O;continue}if("`"==O){m+=h==O?"\\`":O;continue}if("'"==O){m+=h==O?"\\'":O;continue}}if("\0"!=O||y||d.test(_.charAt(I+1)))if(o.test(O))m+=i[O];else{var L=O.charCodeAt(0);if(l.minimal&&8232!=L&&8233!=L)m+=O;else{var F=L.toString(16);b||(F=F.toUpperCase());var U=F.length>2||y,q="\\"+(U?"u":"x")+("0000"+F).slice(U?-4:-2);m+=q}}else m+="\\0"}return l.wrap&&(m=h+m+h),"`"==h&&(m=m.replace(/\$\{/g,"\\${")),l.isScriptContext?m.replace(/<\/(script|style)/gi,"<\\/$1").replace(/|$)/g,"")}function g(e){return e.replace(/\/\*[\s\S]+?\*\//g,"")}function b(e){try{return f(e)}catch{return e}}function v(e){return e.replace(/\s/g,"")}function w(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function _(e,t){const n=f(t),r=`^\\s*?${w(e)}\\(\\s*?\\)`,o=new RegExp(r,"gm").exec(n)??[];return Boolean(o.length)}function x(...e){const t=e.map((e=>new RegExp(e).source)).join("");return new RegExp(t)}function E(e,t,n){const r=n?.capture??!1,o=n?.includeBody??!0,i=t?t.join("\\s*,\\s*"):"[^)]*",a="[^}]*",s=`function\\s*${e?"\\s"+w(e):""}\\s*\\(\\s*${i}\\s*\\)\\s*\\{`,c=`${a}\\}`,u=o?`${s}${c}`:`${s}`,l=`${e?`(let|const|var)?\\s?${w(e)}\\s*=\\s*`:""}\\(?\\s*${i}\\s*\\)?\\s*=>\\s*\\{?`,f=o?`${l}${a}\\}?`:`${l}`,p=`${e?`(let|const|var)?\\s?${w(e)}\\s*=\\s*function\\s*`:""}\\(\\s*${i}\\s*\\)\\s*\\{`;return new RegExp(`(${r?"":"?:"}${u}|${f}|${o?`${p}${c}`:`${p}`})`)}const O=/\(\?<([\w\d]+)>/g,S=/\\k<([\w\d]+)>/g;function A(e,{capture:t=!1,elementsSeparator:n=String.raw`\s*\|\|\s*`,permutationsSeparator:r="|"}={}){const o=function(e){const t=[];return function e(n,r){if(1!==r)for(let t=0;tnew RegExp(e).source))).map(((e,t)=>e.join(n).replace(O,String.raw`(?<$1_${t}>`).replace(S,String.raw`\k<$1_${t}>`))).join(r);return new RegExp(`(${t?"":"?:"}${o})`)}async function T(e,t){globalThis.IS_REACT_ACT_ENVIRONMENT=!0;const n=document.createElement("div"),r=globalThis.React?.createElement(e,t);return await(globalThis.React?.act((async()=>{globalThis.ReactDOM?.createRoot(n).render(r)}))),n}const k={astHelpers:'import ast\n\n# A chainable class that allows us to call functions on the result of parsing a string\n\n\nclass Node:\n def __init__(self, tree=None):\n if isinstance(tree, str):\n self.tree = ast.parse(tree)\n elif isinstance(tree, ast.AST) or tree == None:\n self.tree = tree\n else:\n raise TypeError("Node must be initialized with a string or AST")\n\n def __getitem__(self, i):\n if getattr(self.tree, "__getitem__", False):\n return Node(self.tree[i])\n elif getattr(self.tree, "body", False):\n return Node(self.tree.body[i])\n else:\n raise IndexError("Empty Nodes cannot be indexed.")\n\n def __len__(self):\n if getattr(self.tree, "__len__", False):\n return len(self.tree)\n if self.tree is None:\n return 0\n if not hasattr(self.tree, "body"):\n return 1\n return len(self.tree.body)\n\n def __eq__(self, other):\n if not isinstance(other, Node):\n return False\n if self.tree == None:\n return other.tree == None\n if other.tree == None:\n return False\n return ast.dump(self.tree, include_attributes=True) == ast.dump(\n other.tree, include_attributes=True\n )\n\n def __repr__(self):\n if self.tree == None:\n return "Node:\\nNone"\n return "Node:\\n" + ast.dump(self.tree, indent=2)\n\n def __str__(self):\n if self.tree == None:\n return "# no ast"\n return ast.unparse(self.tree)\n\n def _has_body(self):\n return bool(getattr(self.tree, "body", False))\n\n # "find" functions return a new node with the result of the find\n # function. In this case, it returns a new node with the function\n # definition (if it exists)\n\n def find_function(self, func):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.FunctionDef):\n if node.name == func:\n return Node(node)\n return Node()\n\n def find_functions(self, func):\n return [\n node\n for node in self._find_all((ast.FunctionDef, ast.AsyncFunctionDef))\n if node.tree.name == func\n ]\n\n def find_async_function(self, func):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.AsyncFunctionDef):\n if node.name == func:\n return Node(node)\n return Node()\n\n def find_awaits(self):\n return [\n node\n for node in self._find_all(ast.Expr)\n if isinstance(node.tree.value, ast.Await)\n ]\n\n def has_args(self, arg_str):\n if not isinstance(self.tree, (ast.FunctionDef, ast.AsyncFunctionDef)):\n return False\n dec_list = (f"@{Node(node)}" for node in self.tree.decorator_list)\n dec_str = "\\n".join(dec_list) + "\\n" if dec_list else ""\n if id := getattr(self.tree.returns, "id", False):\n returns = f" -> {id}"\n elif hasattr(self.tree.returns, "value"):\n returns = f" -> {self.tree.returns.value}"\n else:\n returns = ""\n async_kw = ""\n if isinstance(self.tree, ast.AsyncFunctionDef):\n async_kw = "async "\n body_lines = str(self.find_body()).split("\\n")\n new_body = "".join([f"\\n {line}" for line in body_lines])\n func_str = (\n f"{dec_str}{async_kw}def {self.tree.name}({arg_str}){returns}:{new_body}"\n )\n return self.is_equivalent(func_str)\n\n # returns_str is the annotation of the type returned by the function\n def has_returns(self, returns_str):\n if not isinstance(self.tree, (ast.FunctionDef, ast.AsyncFunctionDef)):\n return False\n return Node(self.tree.returns).is_equivalent(returns_str)\n\n def find_body(self):\n if not isinstance(self.tree, ast.AST):\n return Node()\n if not hasattr(self.tree, "body"):\n return Node()\n return Node(ast.Module(self.tree.body, []))\n\n # find the return statement of a function\n def find_return(self):\n if return_list := self._find_all(ast.Return):\n return return_list[0]\n return Node()\n\n def has_return(self, return_value):\n return self.find_return().is_equivalent(f"return {return_value}")\n\n def find_imports(self):\n return self._find_all((ast.Import, ast.ImportFrom))\n\n def find_comps(self):\n return [\n node\n for node in self._find_all(ast.Expr)\n if isinstance(\n node.tree.value,\n (ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp),\n )\n ]\n\n def _find_comp(\n self, classes=(ast.ListComp, ast.SetComp, ast.GeneratorExp, ast.DictComp)\n ):\n if isinstance(self.tree, classes):\n return Node(self.tree)\n elif isinstance(self.tree, (ast.Assign, ast.AnnAssign, ast.Return)):\n if isinstance(self.tree.value, classes):\n return Node(self.tree.value)\n return Node()\n\n # find a list of iterables of a comprehension/generator expression\n def find_comp_iters(self):\n if not (node := self._find_comp()):\n return []\n return [Node(gen.iter) for gen in node.tree.generators]\n\n # find a list of targets (iteration variables) of a comprehension/generator expression\n def find_comp_targets(self):\n if not (node := self._find_comp()):\n return []\n return [Node(gen.target) for gen in node.tree.generators]\n\n # find the key of a dictionary comprehension\n def find_comp_key(self):\n if not (node := self._find_comp(ast.DictComp)):\n return Node()\n return Node(node.tree.key)\n\n # find the expression evaluated for a comprehension/generator expression\n # which is the value of the key in case of a dictionary comprehension\n def find_comp_expr(self):\n if not (node := self._find_comp()):\n return Node()\n if isinstance(node.tree, (ast.ListComp, ast.SetComp, ast.GeneratorExp)):\n return Node(node.tree.elt)\n elif isinstance(node.tree, ast.DictComp):\n return Node(node.tree.value)\n\n # find a list of `IfExpr`s at the end of the comprehension/generator expression\n def find_comp_ifs(self):\n if not (node := self._find_comp()):\n return []\n return [\n Node(gen.ifs[i])\n for gen in node.tree.generators\n for i in range(len(gen.ifs))\n ]\n\n # "has" functions return a boolean indicating whether whatever is being\n # searched for exists. In this case, it returns True if the variable exists.\n\n def has_variable(self, name):\n return self.find_variable(name) != Node()\n\n def has_import(self, import_str):\n return any(\n import_node.is_equivalent(import_str) for import_node in self.find_imports()\n )\n\n # find a list of function calls of the \'name\' function\n def find_calls(self, name):\n call_list = []\n for node in self._find_all(ast.Expr):\n if func := getattr(node.tree.value, "func", False):\n if isinstance(func, ast.Name) and func.id == name:\n call_list.append(Node(node.tree.value))\n elif isinstance(func, ast.Attribute) and func.attr == name:\n call_list.append(Node(node.tree.value))\n return call_list\n\n def has_call(self, call):\n return any(node.is_equivalent(call) for node in self._find_all(ast.Expr))\n\n def block_has_call(self, name, function=None):\n body = self if function is None else self.find_function(function).find_body()\n if body:\n for node in ast.walk(body.tree):\n fields = node._fields\n for n, field in enumerate(fields):\n if field == "attr" or field == "id" and fields[n + 1] == "ctx":\n if getattr(node, field, None) == name and isinstance(\n getattr(node, fields[n + 1], None), ast.Load\n ):\n return True\n return False\n\n def find_call_args(self):\n if not isinstance(self.tree, ast.Call):\n return []\n return [Node(arg) for arg in self.tree.args]\n\n def has_stmt(self, node_str):\n if not self._has_body():\n return False\n return any(Node(node).is_equivalent(node_str) for node in self.tree.body)\n\n def find_variable(self, name):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.Assign):\n for target in node.targets:\n if isinstance(target, ast.Name):\n if target.id == name:\n return Node(node)\n if isinstance(target, ast.Attribute):\n names = name.split(".")\n if target.value.id == names[0] and target.attr == names[1]:\n return Node(node)\n elif isinstance(node, ast.AnnAssign):\n if isinstance(node.target, ast.Name):\n if node.target.id == name:\n return Node(node)\n return Node()\n\n def find_variables(self, name):\n assignments = self._find_all((ast.Assign, ast.AnnAssign))\n var_list = []\n for node in assignments:\n if isinstance(node.tree, ast.Assign):\n for target in node.tree.targets:\n if isinstance(target, ast.Name):\n if target.id == name:\n var_list.append(node)\n if isinstance(target, ast.Attribute):\n names = name.split(".")\n if target.value.id == names[0] and target.attr == names[1]:\n var_list.append(node)\n elif isinstance(node.tree, ast.AnnAssign):\n if isinstance(node.tree.target, ast.Name):\n if node.tree.target.id == name:\n var_list.append(node)\n return var_list\n\n # find variable incremented or decremented using += or -=\n def find_aug_variable(self, name):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.AugAssign):\n if isinstance(node.target, ast.Name):\n if node.target.id == name:\n return Node(node)\n return Node()\n\n def get_variable(self, name):\n var = self.find_variable(name)\n if var != Node():\n return var.tree.value.value\n else:\n return None\n\n def has_function(self, name):\n return self.find_function(name) != Node()\n\n def has_class(self, name):\n return self.find_class(name) != Node()\n\n def has_decorators(self, *args):\n # the order of args does matter\n if not isinstance(self.tree, (ast.FunctionDef, ast.AsyncFunctionDef)):\n return False\n dec_list = (Node(node) for node in self.tree.decorator_list)\n return all(any(dec.is_equivalent(arg) for dec in dec_list) for arg in args)\n\n # Checks if the current scope contains a "pass" statement\n\n def has_pass(self):\n if isinstance(self.tree, (ast.If, ast.While, ast.For)):\n return False\n if getattr(self.tree, "body", False):\n return any(isinstance(node, ast.Pass) for node in self.tree.body)\n return False\n\n # Checks the variable, name, is in the current scope and is an integer\n\n def is_integer(self):\n if not isinstance(self.tree, ast.Assign):\n return False\n return type(self.tree.value.value) == type(1)\n\n def value_is_call(self, name):\n if not isinstance(self.tree, ast.Assign):\n return False\n call = self.tree.value\n if isinstance(call, ast.Call):\n return call.func.id == name\n return False\n\n # Loosely compares the code in target_str with the code represented by the\n # Node\'s AST. If the two codes are semantically equivalent (i.e. the same if\n # you ignore formatting and context) then this returns True, otherwise\n # False.\n #\n # Ignoring context means that the following comparison is True despite the\n # fact that the AST of `cond_node` is `Constant(value=True)` and `True`\n # compiles to `Module(body=[Expr(value=Constant(value=True))],\n # type_ignores=[])`:\n #\n # node = Node("if True:\\n pass") cond_node =\n # node.find_ifs()[0].find_conditions()[0] cond_node.is_equivalent("True")\n\n def is_equivalent(self, target_str):\n # Setting the tree to None is used to represent missing elements. Such\n # as the condition of a final else clause. It is, therefore, not\n # equivalent to any string.\n if self.tree == None:\n return False\n code_str = ast.unparse(self.tree)\n\n # Why parse and unparse again? Because of an edge case when comparing\n # the `target_str` "\'True\'" with the test in "if \'True\':". These should\n # be equivalent, but the condition unparses to "\'True\'", while the\n # `target_str` becomes \'"""True"""\' when parsed and unparsed again.\n\n # By parsing and unparsing `code_str` we get \'"""True"""\' and the\n # comparison returns True as expected.\n return ast.unparse(ast.parse(code_str)) == ast.unparse(ast.parse(target_str))\n\n def is_empty(self):\n return self.tree == None\n\n # Finds the class definition with the given name\n\n def find_class(self, class_name):\n if not self._has_body():\n return Node()\n for node in self.tree.body:\n if isinstance(node, ast.ClassDef):\n if node.name == class_name:\n return Node(node)\n return Node()\n\n def inherits_from(self, *args):\n if not isinstance(self.tree, ast.ClassDef):\n return False\n if not self.tree.bases:\n return False\n id_list = [\n node.id if isinstance(node, ast.Name) else f"{node.value.id}.{node.attr}"\n for node in self.tree.bases\n ]\n return all(arg in id_list for arg in args)\n\n # Find an array of if statements\n\n def find_ifs(self):\n return self._find_all(ast.If)\n\n def _find_all(self, ast_type):\n return [Node(node) for node in self.tree.body if isinstance(node, ast_type)]\n\n def find_whiles(self):\n return self._find_all(ast.While)\n\n def find_for_loops(self):\n return self._find_all(ast.For)\n\n def find_for_vars(self):\n if not isinstance(self.tree, ast.For):\n return Node()\n return Node(self.tree.target)\n\n def find_for_iter(self):\n if not isinstance(self.tree, ast.For):\n return Node()\n return Node(self.tree.iter)\n\n def find_if(self, if_str):\n if_list = self._find_all(ast.If)\n for if_statement in if_list:\n if if_statement.find_conditions()[0].is_equivalent(if_str):\n return if_statement\n return Node()\n\n def find_while(self, while_str):\n while_list = self._find_all(ast.While)\n for while_loop in while_list:\n if while_loop.find_conditions()[0].is_equivalent(while_str):\n return while_loop\n return Node()\n\n def find_for(self, target_str, iter_str):\n for_list = self._find_all(ast.For)\n for for_loop in for_list:\n if for_loop.find_for_vars().is_equivalent(\n target_str\n ) and for_loop.find_for_iter().is_equivalent(iter_str):\n return for_loop\n return Node()\n\n # Find an array of bodies in if/elif statement and while or for loops\n\n def find_bodies(self):\n def _find_bodies(tree):\n if not isinstance(tree, (ast.If, ast.While, ast.For)):\n return []\n if tree.orelse == []:\n return [tree.body]\n if isinstance(tree.orelse[0], (ast.If, ast.While, ast.For)):\n return [tree.body] + _find_bodies(tree.orelse[0])\n\n return [tree.body] + [tree.orelse]\n\n return [Node(ast.Module(body, [])) for body in _find_bodies(self.tree)]\n\n # Find an array of conditions in if/elif statement or while loop\n\n def find_conditions(self):\n def _find_conditions(tree):\n if not isinstance(tree, (ast.If, ast.While)):\n return []\n test = tree.test\n if tree.orelse == []:\n return [test]\n if isinstance(tree.orelse[0], (ast.If, ast.While)):\n return [test] + _find_conditions(tree.orelse[0])\n\n return [test, None]\n\n return [Node(test) for test in _find_conditions(self.tree)]\n\n def find_matches(self):\n return self._find_all(ast.Match)\n\n def find_match_subject(self):\n if not isinstance(self.tree, ast.Match):\n return Node()\n return Node(self.tree.subject)\n\n def find_match_cases(self):\n if not isinstance(self.tree, ast.Match):\n return []\n return [Node(case) for case in self.tree.cases]\n\n def find_case_pattern(self):\n if not isinstance(self.tree, ast.match_case):\n return Node()\n return Node(self.tree.pattern)\n\n def find_case_guard(self):\n if not isinstance(self.tree, ast.match_case):\n return Node()\n if guard := getattr(self.tree, "guard", False):\n return Node(guard)\n return Node()\n\n def find_trys(self):\n return self._find_all(ast.Try)\n\n def find_excepts(self):\n if not isinstance(self.tree, ast.Try):\n return []\n return [Node(handler) for handler in self.tree.handlers]\n\n def find_except(self, except_type=None, name=None):\n if not isinstance(self.tree, ast.Try):\n return Node()\n for handler in self.tree.handlers:\n if except_type is None and handler.type is None:\n return Node(handler)\n if isinstance(handler.type, ast.Name):\n if handler.type.id == except_type:\n if (name is None and handler.name is None) or handler.name == name:\n return Node(handler)\n return Node()\n\n def has_except(self, except_type=None, name=None):\n if self.find_except(except_type, name) == Node():\n return False\n return True\n\n def find_try_else(self):\n if not isinstance(self.tree, ast.Try):\n return Node()\n if not self.tree.orelse:\n return Node()\n return Node(ast.Module(self.tree.orelse, []))\n\n def find_finally(self):\n if not isinstance(self.tree, ast.Try):\n return Node()\n if not self.tree.finalbody:\n return Node()\n return Node(ast.Module(self.tree.finalbody, []))\n\n # Returs a Boolean indicating if the statements passed as arguments\n # are found in the same order in the tree (statements can be non-consecutive)\n def is_ordered(self, *args):\n if not self._has_body():\n return False\n if len(args) < 2:\n return False\n arg_dict = {key: None for key in range(len(args))}\n for i, node in enumerate(self.tree.body):\n for j, arg in enumerate(args):\n if Node(node).is_equivalent(arg):\n arg_dict[j] = i\n break\n if None in arg_dict.values():\n return False\n return all(arg_dict[n] < arg_dict[n + 1] for n in range(len(arg_dict) - 1))\n',getDef(e,t){const n=new RegExp(`\\n?(? *?)def +${t} *\\((?[^\\)]*)\\)\\s*:\\s*?\\n?(?.*?)(?=\\n\\k[\\w#]|$)`,"s").exec(e);if(n){const{function_parameters:e,function_body:t,function_indentation:r}=n.groups,o=r.replace(/\n+/,"");return{def:n[0].replace(/^\n/,""),function_parameters:e,function_body:t,function_indentation:o.length}}return null},removeComments:e=>e.replace(/\/\/.*|\/\*[\s\S]*?\*\/|(#.*$)/gm,""),getBlock(e,t){const n=t instanceof RegExp?t.source:t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=new RegExp(`\\n?(? *?)(?${n})\\s*:\\s*?\\n(?(\\k +[^\\n]*| *\\n)+)(\n|$)`,"sm").exec(e);if(r){const{block_body:e,block_indentation:t,block_condition:n}=r.groups;return{block_body:e,block_condition:n,block_indentation:t.replace(/\n+/g,"").length}}return null}};class j{doc;constructor(e){this.doc=e}_getStyleRules(){const e=this.getStyleSheet();return this.styleSheetToCssRulesArray(e).filter((e=>e.type===CSSRule.STYLE_RULE))}getStyleDeclarations(e){return this._getStyleRules()?.filter((t=>t?.selectorText===e)).map((e=>e.style))}getStyle(e){const t=this._getStyleRules().find((t=>t?.selectorText===e))?.style;return t?(t.getPropVal=(e,n=!1)=>n?t.getPropertyValue(e).replace(/\s+/g,""):t.getPropertyValue(e),t):null}getStyleAny(e){for(const t of e){const e=this.getStyle(t);if(e)return e}return null}getStyleRule(e){const t=this._getStyleRules()?.find((t=>t?.selectorText===e));return t?{...t,isDeclaredAfter:e=>(e=>t=>{const n=Array.from(e.parentStyleSheet?.cssRules||[])?.filter((e=>e.type===CSSRule.STYLE_RULE)),r=n.find((e=>e?.selectorText===t));return!!r&&Array.from(e.parentStyleSheet?.cssRules||[]).indexOf(e)>Array.from(r?.parentStyleSheet?.cssRules||[]).indexOf(r)})(t)(e)}:null}getCSSRules(e){const t=this.getStyleSheet(),n=this.styleSheetToCssRulesArray(t);switch(e){case"media":return n.filter((e=>e.type===CSSRule.MEDIA_RULE));case"fontface":return n.filter((e=>e.type===CSSRule.FONT_FACE_RULE));case"import":return n.filter((e=>e.type===CSSRule.IMPORT_RULE));case"keyframes":return n.filter((e=>e.type===CSSRule.KEYFRAMES_RULE));default:return n}}isPropertyUsed(e){return this._getStyleRules().some((t=>t.style?.getPropertyValue(e)))}getRuleListsWithinMedia(e){const t=this.getCSSRules("media"),n=t?.find((t=>t?.media?.mediaText===e)),r=n?.cssRules;return Array.from(r||[])}getStyleSheet(){const e=this.doc?.querySelector("link[href*='styles']"),t=this.doc?.querySelector("style.fcc-injected-styles"),n=this.doc?.querySelector("style:not([class]):not([media])");return e?.sheet?.cssRules?.length?e.sheet:t?t.sheet:n?n.sheet:null}styleSheetToCssRulesArray(e){return Array.from(e?.cssRules||[])}selectorsFromSelector(e){const t=this.doc.querySelectorAll(e),n=Array.from(t).map((e=>{const t=[],n=[],r=[];for(;e.parentNode;){let o=e.tagName.toLowerCase();Array.from(e.parentNode.children).filter((t=>t.tagName===e.tagName)).length>1&&(o+=`:nth-child(${Array.from(e.parentNode.childNodes).indexOf(e)+1})`),t.unshift(o),n.unshift(o),r.push([t.join(" > "),n.join(" ")]),e=e.parentNode}return r.flat()})).flat();return[...new Set(n)]}}function N(e){const t=e.match(/(?:function\s*[^(]*\(([^)]*)\))/)||e.match(/(?:\b(?:const|let|var)\s*\w+\s*=\s*(?:function)?\s*\(([^)]*)\))/)||e.match(/=\s+([^)]*)=>/);return t?(t[1]||t[2]||t[3]||t[4]).replace(/[{}[\]]/g,"").split(",").map((e=>{const t=e.trim().split(/[=]/),n=t.length>1?t[1].replace(/['"]/g,"").trim():void 0;return{name:t[0].trim(),defaultValue:n}})):[]}function P(e,t,n=20){return n<1?Promise.reject(new Error(t)):e()?Promise.resolve():new Promise(((r,o)=>{setTimeout((()=>{P(e,t,n-1).then(r).catch(o)}),1)}))}},93531(e,t,n){"use strict";var r=n(13583),o=n(81948),i=n(75674);e.exports=function(){return i(r,o,arguments)}},93977(e,t,n){"use strict";var r=n(91242).every;function o(e,t,n,r){var o=!0;return n!==r.length-1&&(o=t.calledBefore(r[n+1])),!(!function(e,t){return void 0===e[t.id]&&(e[t.id]=0),e[t.id]1?arguments:e,o.bind(null,{}))}},94930(e,t,n){"use strict";var r=Function.call,o=n(32365),i=["size","caller","callee","arguments"];o&&i.push("__proto__"),e.exports=function(e){return Object.getOwnPropertyNames(e).reduce((function(t,n){return i.includes(n)||"function"!=typeof e[n]||(t[n]=r.bind(e[n])),t}),Object.create(null))}},95809(e){"use strict";e.exports=["Float16Array","Float32Array","Float64Array","Int8Array","Int16Array","Int32Array","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array"]},95938(e){var t=Object.getOwnPropertyDescriptor((function(){}),"length");e.exports=function(e,n,r){return t.configurable?(Object.defineProperty(e,"length",{get:function(){if(r)throw Error("Invalid Chai property: "+n+'.length. Due to a compatibility issue, "length" cannot directly follow "'+n+'". Use "'+n+'.lengthOf" instead.');throw Error("Invalid Chai property: "+n+'.length. See docs for proper usage of "'+n+'".')}}),e):e}},99183(e){"use strict";e.exports=Error},99427(e){e.exports=function(e,t){var n=e.Assertion,r=e.AssertionError,o=t.flag;function i(e,n){n&&o(this,"message",n),e=e.toLowerCase();var r=o(this,"object"),i=~["a","e","i","o","u"].indexOf(e.charAt(0))?"an ":"a ";this.assert(e===t.type(r).toLowerCase(),"expected #{this} to be "+i+e,"expected #{this} not to be "+i+e)}function a(e,n){return t.isNaN(e)&&t.isNaN(n)||e===n}function s(){o(this,"contains",!0)}function c(e,i){i&&o(this,"message",i);var s=o(this,"object"),c=t.type(s).toLowerCase(),u=o(this,"message"),l=o(this,"negate"),f=o(this,"ssfi"),p=o(this,"deep"),d=p?"deep ":"",h=p?o(this,"eql"):a;u=u?u+": ":"";var y=!1;switch(c){case"string":y=-1!==s.indexOf(e);break;case"weakset":if(p)throw new r(u+"unable to use .deep.include with WeakSet",void 0,f);y=s.has(e);break;case"map":s.forEach((function(t){y=y||h(t,e)}));break;case"set":p?s.forEach((function(t){y=y||h(t,e)})):y=s.has(e);break;case"array":y=p?s.some((function(t){return h(t,e)})):-1!==s.indexOf(e);break;default:if(e!==Object(e))throw new r(u+"the given combination of arguments ("+c+" and "+t.type(e).toLowerCase()+") is invalid for this assertion. You can use an array, a map, an object, a set, a string, or a weakset instead of a "+t.type(e).toLowerCase(),void 0,f);var m=Object.keys(e),g=null,b=0;if(m.forEach((function(i){var a=new n(s);if(t.transferFlags(this,a,!0),o(a,"lockSsfi",!0),l&&1!==m.length)try{a.property(i,e[i])}catch(e){if(!t.checkError.compatibleConstructor(e,r))throw e;null===g&&(g=e),b++}else a.property(i,e[i])}),this),l&&m.length>1&&b===m.length)throw g;return}this.assert(y,"expected #{this} to "+d+"include "+t.inspect(e),"expected #{this} to not "+d+"include "+t.inspect(e))}function u(){var e=o(this,"object");this.assert(null!=e,"expected #{this} to exist","expected #{this} to not exist")}function l(){var e=o(this,"object"),n=t.type(e);this.assert("Arguments"===n,"expected #{this} to be arguments but got "+n,"expected #{this} to not be arguments")}function f(e,t){t&&o(this,"message",t);var n=o(this,"object");if(o(this,"deep")){var r=o(this,"lockSsfi");o(this,"lockSsfi",!0),this.eql(e),o(this,"lockSsfi",r)}else this.assert(e===n,"expected #{this} to equal #{exp}","expected #{this} to not equal #{exp}",e,this._obj,!0)}function p(e,t){t&&o(this,"message",t);var n=o(this,"eql");this.assert(n(e,o(this,"object")),"expected #{this} to deeply equal #{exp}","expected #{this} to not deeply equal #{exp}",e,this._obj,!0)}function d(e,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"doLength"),u=o(this,"message"),l=u?u+": ":"",f=o(this,"ssfi"),p=t.type(s).toLowerCase(),d=t.type(e).toLowerCase(),h=!0;if(c&&"map"!==p&&"set"!==p&&new n(s,u,f,!0).to.have.property("length"),c||"date"!==p||"date"===d?"number"===d||!c&&"number"!==p?c||"date"===p||"number"===p?h=!1:a=l+"expected "+("string"===p?"'"+s+"'":s)+" to be a number or a date":a=l+"the argument to above must be a number":a=l+"the argument to above must be a date",h)throw new r(a,void 0,f);if(c){var y,m="length";"map"===p||"set"===p?(m="size",y=s.size):y=s.length,this.assert(y>e,"expected #{this} to have a "+m+" above #{exp} but got #{act}","expected #{this} to not have a "+m+" above #{exp}",e,y)}else this.assert(s>e,"expected #{this} to be above #{exp}","expected #{this} to be at most #{exp}",e)}function h(e,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"doLength"),u=o(this,"message"),l=u?u+": ":"",f=o(this,"ssfi"),p=t.type(s).toLowerCase(),d=t.type(e).toLowerCase(),h=!0;if(c&&"map"!==p&&"set"!==p&&new n(s,u,f,!0).to.have.property("length"),c||"date"!==p||"date"===d?"number"===d||!c&&"number"!==p?c||"date"===p||"number"===p?h=!1:a=l+"expected "+("string"===p?"'"+s+"'":s)+" to be a number or a date":a=l+"the argument to least must be a number":a=l+"the argument to least must be a date",h)throw new r(a,void 0,f);if(c){var y,m="length";"map"===p||"set"===p?(m="size",y=s.size):y=s.length,this.assert(y>=e,"expected #{this} to have a "+m+" at least #{exp} but got #{act}","expected #{this} to have a "+m+" below #{exp}",e,y)}else this.assert(s>=e,"expected #{this} to be at least #{exp}","expected #{this} to be below #{exp}",e)}function y(e,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"doLength"),u=o(this,"message"),l=u?u+": ":"",f=o(this,"ssfi"),p=t.type(s).toLowerCase(),d=t.type(e).toLowerCase(),h=!0;if(c&&"map"!==p&&"set"!==p&&new n(s,u,f,!0).to.have.property("length"),c||"date"!==p||"date"===d?"number"===d||!c&&"number"!==p?c||"date"===p||"number"===p?h=!1:a=l+"expected "+("string"===p?"'"+s+"'":s)+" to be a number or a date":a=l+"the argument to below must be a number":a=l+"the argument to below must be a date",h)throw new r(a,void 0,f);if(c){var y,m="length";"map"===p||"set"===p?(m="size",y=s.size):y=s.length,this.assert(ye===t,b="";d&&(b+="deep "),s&&(b+="own "),a&&(b+="nested "),b+="property ",p=s?Object.prototype.hasOwnProperty.call(u,e):a?y.exists:t.hasProperty(u,e),h&&1!==arguments.length||this.assert(p,"expected #{this} to have "+b+t.inspect(e),"expected #{this} to not have "+b+t.inspect(e)),arguments.length>1&&this.assert(p&&g(n,m),"expected #{this} to have "+b+t.inspect(e)+" of #{exp}, but got #{act}","expected #{this} to not have "+b+t.inspect(e)+" of #{act}",n,m),o(this,"object",m)}function v(e,t,n){o(this,"own",!0),b.apply(this,arguments)}function w(e,n,r){"string"==typeof n&&(r=n,n=null),r&&o(this,"message",r);var i=o(this,"object"),a=Object.getOwnPropertyDescriptor(Object(i),e),s=o(this,"eql");a&&n?this.assert(s(n,a),"expected the own property descriptor for "+t.inspect(e)+" on #{this} to match "+t.inspect(n)+", got "+t.inspect(a),"expected the own property descriptor for "+t.inspect(e)+" on #{this} to not match "+t.inspect(n),n,a,!0):this.assert(a,"expected #{this} to have an own property descriptor for "+t.inspect(e),"expected #{this} to not have an own property descriptor for "+t.inspect(e)),o(this,"object",a)}function _(){o(this,"doLength",!0)}function x(e,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=t.type(a).toLowerCase(),c=o(this,"message"),u=o(this,"ssfi"),l="length";switch(s){case"map":case"set":l="size",i=a.size;break;default:new n(a,c,u,!0).to.have.property("length"),i=a.length}this.assert(i==e,"expected #{this} to have a "+l+" of #{exp} but got #{act}","expected #{this} to not have a "+l+" of #{act}",e,i)}function E(e,t){t&&o(this,"message",t);var n=o(this,"object");this.assert(e.exec(n),"expected #{this} to match "+e,"expected #{this} not to match "+e)}function O(e){var n,i,a=o(this,"object"),s=t.type(a),c=t.type(e),u=o(this,"ssfi"),l=o(this,"deep"),f="",p=!0,d=o(this,"message"),h=(d=d?d+": ":"")+"when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments";if("Map"===s||"Set"===s)f=l?"deeply ":"",i=[],a.forEach((function(e,t){i.push(t)})),"Array"!==c&&(e=Array.prototype.slice.call(arguments));else{switch(i=t.getOwnEnumerableProperties(a),c){case"Array":if(arguments.length>1)throw new r(h,void 0,u);break;case"Object":if(arguments.length>1)throw new r(h,void 0,u);e=Object.keys(e);break;default:e=Array.prototype.slice.call(arguments)}e=e.map((function(e){return"symbol"==typeof e?e:String(e)}))}if(!e.length)throw new r(d+"keys required",void 0,u);var y=e.length,m=o(this,"any"),g=o(this,"all"),b=e,v=l?o(this,"eql"):(e,t)=>e===t;if(m||g||(g=!0),m&&(p=b.some((function(e){return i.some((function(t){return v(e,t)}))}))),g&&(p=b.every((function(e){return i.some((function(t){return v(e,t)}))})),o(this,"contains")||(p=p&&e.length==i.length)),y>1){var w=(e=e.map((function(e){return t.inspect(e)}))).pop();g&&(n=e.join(", ")+", and "+w),m&&(n=e.join(", ")+", or "+w)}else n=t.inspect(e[0]);n=(y>1?"keys ":"key ")+n,n=(o(this,"contains")?"contain ":"have ")+n,this.assert(p,"expected #{this} to "+f+n,"expected #{this} to not "+f+n,b.slice(0).sort(t.compareByInspect),i.sort(t.compareByInspect),!0)}function S(e,r,i){i&&o(this,"message",i);var a,s=o(this,"object"),c=o(this,"ssfi"),u=o(this,"message"),l=o(this,"negate")||!1;new n(s,u,c,!0).is.a("function"),(e instanceof RegExp||"string"==typeof e)&&(r=e,e=null);try{s()}catch(e){a=e}var f=void 0===e&&void 0===r,p=Boolean(e&&r),d=!1,h=!1;if(f||!f&&!l){var y="an error";e instanceof Error?y="#{exp}":e&&(y=t.checkError.getConstructorName(e)),this.assert(a,"expected #{this} to throw "+y,"expected #{this} to not throw an error but #{act} was thrown",e&&e.toString(),a instanceof Error?a.toString():"string"==typeof a?a:a&&t.checkError.getConstructorName(a))}if(e&&a&&(e instanceof Error&&t.checkError.compatibleInstance(a,e)===l&&(p&&l?d=!0:this.assert(l,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(a&&!l?" but #{act} was thrown":""),e.toString(),a.toString())),t.checkError.compatibleConstructor(a,e)===l&&(p&&l?d=!0:this.assert(l,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(a?" but #{act} was thrown":""),e instanceof Error?e.toString():e&&t.checkError.getConstructorName(e),a instanceof Error?a.toString():a&&t.checkError.getConstructorName(a)))),a&&null!=r){var m="including";r instanceof RegExp&&(m="matching"),t.checkError.compatibleMessage(a,r)===l&&(p&&l?h=!0:this.assert(l,"expected #{this} to throw error "+m+" #{exp} but got #{act}","expected #{this} to throw error not "+m+" #{exp}",r,t.checkError.getMessage(a)))}d&&h&&this.assert(l,"expected #{this} to throw #{exp} but #{act} was thrown","expected #{this} to not throw #{exp}"+(a?" but #{act} was thrown":""),e instanceof Error?e.toString():e&&t.checkError.getConstructorName(e),a instanceof Error?a.toString():a&&t.checkError.getConstructorName(a)),o(this,"object",a)}function A(e,n){n&&o(this,"message",n);var r=o(this,"object"),i=o(this,"itself"),a="function"!=typeof r||i?r[e]:r.prototype[e];this.assert("function"==typeof a,"expected #{this} to respond to "+t.inspect(e),"expected #{this} to not respond to "+t.inspect(e))}function T(e,n){n&&o(this,"message",n);var r=e(o(this,"object"));this.assert(r,"expected #{this} to satisfy "+t.objDisplay(e),"expected #{this} to not satisfy"+t.objDisplay(e),!o(this,"negate"),r)}function k(e,t,i){i&&o(this,"message",i);var a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");if(new n(a,s,c,!0).is.a("number"),"number"!=typeof e||"number"!=typeof t)throw new r((s=s?s+": ":"")+"the arguments to closeTo or approximately must be numbers"+(void 0===t?", and a delta is required":""),void 0,c);this.assert(Math.abs(a-e)<=t,"expected #{this} to be close to "+e+" +/- "+t,"expected #{this} not to be close to "+e+" +/- "+t)}function j(e,t,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");new n(a,s,c,!0).is.a("function"),t?(new n(e,s,c,!0).to.have.property(t),i=e[t]):(new n(e,s,c,!0).is.a("function"),i=e()),a();var u=null==t?e():e[t],l=null==t?i:"."+t;o(this,"deltaMsgObj",l),o(this,"initialDeltaValue",i),o(this,"finalDeltaValue",u),o(this,"deltaBehavior","change"),o(this,"realDelta",u!==i),this.assert(i!==u,"expected "+l+" to change","expected "+l+" to not change")}function N(e,t,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");new n(a,s,c,!0).is.a("function"),t?(new n(e,s,c,!0).to.have.property(t),i=e[t]):(new n(e,s,c,!0).is.a("function"),i=e()),new n(i,s,c,!0).is.a("number"),a();var u=null==t?e():e[t],l=null==t?i:"."+t;o(this,"deltaMsgObj",l),o(this,"initialDeltaValue",i),o(this,"finalDeltaValue",u),o(this,"deltaBehavior","increase"),o(this,"realDelta",u-i),this.assert(u-i>0,"expected "+l+" to increase","expected "+l+" to not increase")}function P(e,t,r){r&&o(this,"message",r);var i,a=o(this,"object"),s=o(this,"message"),c=o(this,"ssfi");new n(a,s,c,!0).is.a("function"),t?(new n(e,s,c,!0).to.have.property(t),i=e[t]):(new n(e,s,c,!0).is.a("function"),i=e()),new n(i,s,c,!0).is.a("number"),a();var u=null==t?e():e[t],l=null==t?i:"."+t;o(this,"deltaMsgObj",l),o(this,"initialDeltaValue",i),o(this,"finalDeltaValue",u),o(this,"deltaBehavior","decrease"),o(this,"realDelta",i-u),this.assert(u-i<0,"expected "+l+" to decrease","expected "+l+" to not decrease")}["to","be","been","is","and","has","have","with","that","which","at","of","same","but","does","still","also"].forEach((function(e){n.addProperty(e)})),n.addProperty("not",(function(){o(this,"negate",!0)})),n.addProperty("deep",(function(){o(this,"deep",!0)})),n.addProperty("nested",(function(){o(this,"nested",!0)})),n.addProperty("own",(function(){o(this,"own",!0)})),n.addProperty("ordered",(function(){o(this,"ordered",!0)})),n.addProperty("any",(function(){o(this,"any",!0),o(this,"all",!1)})),n.addProperty("all",(function(){o(this,"all",!0),o(this,"any",!1)})),n.addChainableMethod("an",i),n.addChainableMethod("a",i),n.addChainableMethod("include",c,s),n.addChainableMethod("contain",c,s),n.addChainableMethod("contains",c,s),n.addChainableMethod("includes",c,s),n.addProperty("ok",(function(){this.assert(o(this,"object"),"expected #{this} to be truthy","expected #{this} to be falsy")})),n.addProperty("true",(function(){this.assert(!0===o(this,"object"),"expected #{this} to be true","expected #{this} to be false",!o(this,"negate"))})),n.addProperty("false",(function(){this.assert(!1===o(this,"object"),"expected #{this} to be false","expected #{this} to be true",!!o(this,"negate"))})),n.addProperty("null",(function(){this.assert(null===o(this,"object"),"expected #{this} to be null","expected #{this} not to be null")})),n.addProperty("undefined",(function(){this.assert(void 0===o(this,"object"),"expected #{this} to be undefined","expected #{this} not to be undefined")})),n.addProperty("NaN",(function(){this.assert(t.isNaN(o(this,"object")),"expected #{this} to be NaN","expected #{this} not to be NaN")})),n.addProperty("exist",u),n.addProperty("exists",u),n.addProperty("empty",(function(){var e,n=o(this,"object"),i=o(this,"ssfi"),a=o(this,"message");switch(a=a?a+": ":"",t.type(n).toLowerCase()){case"array":case"string":e=n.length;break;case"map":case"set":e=n.size;break;case"weakmap":case"weakset":throw new r(a+".empty was passed a weak collection",void 0,i);case"function":var s=a+".empty was passed a function "+t.getName(n);throw new r(s.trim(),void 0,i);default:if(n!==Object(n))throw new r(a+".empty was passed non-string primitive "+t.inspect(n),void 0,i);e=Object.keys(n).length}this.assert(0===e,"expected #{this} to be empty","expected #{this} not to be empty")})),n.addProperty("arguments",l),n.addProperty("Arguments",l),n.addMethod("equal",f),n.addMethod("equals",f),n.addMethod("eq",f),n.addMethod("eql",p),n.addMethod("eqls",p),n.addMethod("above",d),n.addMethod("gt",d),n.addMethod("greaterThan",d),n.addMethod("least",h),n.addMethod("gte",h),n.addMethod("greaterThanOrEqual",h),n.addMethod("below",y),n.addMethod("lt",y),n.addMethod("lessThan",y),n.addMethod("most",m),n.addMethod("lte",m),n.addMethod("lessThanOrEqual",m),n.addMethod("within",(function(e,i,a){a&&o(this,"message",a);var s,c=o(this,"object"),u=o(this,"doLength"),l=o(this,"message"),f=l?l+": ":"",p=o(this,"ssfi"),d=t.type(c).toLowerCase(),h=t.type(e).toLowerCase(),y=t.type(i).toLowerCase(),m=!0,g="date"===h&&"date"===y?e.toISOString()+".."+i.toISOString():e+".."+i;if(u&&"map"!==d&&"set"!==d&&new n(c,l,p,!0).to.have.property("length"),u||"date"!==d||"date"===h&&"date"===y?"number"===h&&"number"===y||!u&&"number"!==d?u||"date"===d||"number"===d?m=!1:s=f+"expected "+("string"===d?"'"+c+"'":c)+" to be a number or a date":s=f+"the arguments to within must be numbers":s=f+"the arguments to within must be dates",m)throw new r(s,void 0,p);if(u){var b,v="length";"map"===d||"set"===d?(v="size",b=c.size):b=c.length,this.assert(b>=e&&b<=i,"expected #{this} to have a "+v+" within "+g,"expected #{this} to not have a "+v+" within "+g)}else this.assert(c>=e&&c<=i,"expected #{this} to be within "+g,"expected #{this} to not be within "+g)})),n.addMethod("instanceof",g),n.addMethod("instanceOf",g),n.addMethod("property",b),n.addMethod("ownProperty",v),n.addMethod("haveOwnProperty",v),n.addMethod("ownPropertyDescriptor",w),n.addMethod("haveOwnPropertyDescriptor",w),n.addChainableMethod("length",x,_),n.addChainableMethod("lengthOf",x,_),n.addMethod("match",E),n.addMethod("matches",E),n.addMethod("string",(function(e,r){r&&o(this,"message",r);var i=o(this,"object"),a=o(this,"message"),s=o(this,"ssfi");new n(i,a,s,!0).is.a("string"),this.assert(~i.indexOf(e),"expected #{this} to contain "+t.inspect(e),"expected #{this} to not contain "+t.inspect(e))})),n.addMethod("keys",O),n.addMethod("key",O),n.addMethod("throw",S),n.addMethod("throws",S),n.addMethod("Throw",S),n.addMethod("respondTo",A),n.addMethod("respondsTo",A),n.addProperty("itself",(function(){o(this,"itself",!0)})),n.addMethod("satisfy",T),n.addMethod("satisfies",T),n.addMethod("closeTo",k),n.addMethod("approximately",k),n.addMethod("members",(function(e,t){t&&o(this,"message",t);var r=o(this,"object"),i=o(this,"message"),a=o(this,"ssfi");new n(r,i,a,!0).to.be.an("array"),new n(e,i,a,!0).to.be.an("array");var s,c,u,l=o(this,"contains"),f=o(this,"ordered");l?(c="expected #{this} to be "+(s=f?"an ordered superset":"a superset")+" of #{exp}",u="expected #{this} to not be "+s+" of #{exp}"):(c="expected #{this} to have the same "+(s=f?"ordered members":"members")+" as #{exp}",u="expected #{this} to not have the same "+s+" as #{exp}");var p=o(this,"deep")?o(this,"eql"):void 0;this.assert(function(e,t,n,r,o){if(!r){if(e.length!==t.length)return!1;t=t.slice()}return e.every((function(e,i){if(o)return n?n(e,t[i]):e===t[i];if(!n){var a=t.indexOf(e);return-1!==a&&(r||t.splice(a,1),!0)}return t.some((function(o,i){return!!n(e,o)&&(r||t.splice(i,1),!0)}))}))}(e,r,p,l,f),c,u,e,r,!0)})),n.addMethod("oneOf",(function(e,t){t&&o(this,"message",t);var r=o(this,"object"),i=o(this,"message"),a=o(this,"ssfi"),s=o(this,"contains"),c=o(this,"deep"),u=o(this,"eql");new n(e,i,a,!0).to.be.an("array"),s?this.assert(e.some((function(e){return r.indexOf(e)>-1})),"expected #{this} to contain one of #{exp}","expected #{this} to not contain one of #{exp}",e,r):c?this.assert(e.some((function(e){return u(r,e)})),"expected #{this} to deeply equal one of #{exp}","expected #{this} to deeply equal one of #{exp}",e,r):this.assert(e.indexOf(r)>-1,"expected #{this} to be one of #{exp}","expected #{this} to not be one of #{exp}",e,r)})),n.addMethod("change",j),n.addMethod("changes",j),n.addMethod("increase",N),n.addMethod("increases",N),n.addMethod("decrease",P),n.addMethod("decreases",P),n.addMethod("by",(function(e,t){t&&o(this,"message",t);var n,r=o(this,"deltaMsgObj"),i=o(this,"initialDeltaValue"),a=o(this,"finalDeltaValue"),s=o(this,"deltaBehavior"),c=o(this,"realDelta");n="change"===s?Math.abs(a-i)===Math.abs(e):c===Math.abs(e),this.assert(n,"expected "+r+" to "+s+" by "+e,"expected "+r+" to not "+s+" by "+e)})),n.addProperty("extensible",(function(){var e=o(this,"object"),t=e===Object(e)&&Object.isExtensible(e);this.assert(t,"expected #{this} to be extensible","expected #{this} to not be extensible")})),n.addProperty("sealed",(function(){var e=o(this,"object"),t=e!==Object(e)||Object.isSealed(e);this.assert(t,"expected #{this} to be sealed","expected #{this} to not be sealed")})),n.addProperty("frozen",(function(){var e=o(this,"object"),t=e!==Object(e)||Object.isFrozen(e);this.assert(t,"expected #{this} to be frozen","expected #{this} to not be frozen")})),n.addProperty("finite",(function(e){var t=o(this,"object");this.assert("number"==typeof t&&isFinite(t),"expected #{this} to be a finite number","expected #{this} to not be a finite number")}))}}},__webpack_module_cache__={},leafPrototypes,getProto,inProgress,dataWebpackPrefix;function __webpack_require__(e){var t=__webpack_module_cache__[e];if(void 0!==t)return t.exports;var n=__webpack_module_cache__[e]={id:e,loaded:!1,exports:{}};return __webpack_modules__[e].call(n.exports,n,n.exports,__webpack_require__),n.loaded=!0,n.exports}__webpack_require__.m=__webpack_modules__,__webpack_require__.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(t,{a:t}),t},getProto=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,__webpack_require__.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var n=Object.create(null);__webpack_require__.r(n);var r={};leafPrototypes=leafPrototypes||[null,getProto({}),getProto([]),getProto(getProto)];for(var o=2&t&&e;("object"==typeof o||"function"==typeof o)&&!~leafPrototypes.indexOf(o);o=getProto(o))Object.getOwnPropertyNames(o).forEach((t=>r[t]=()=>e[t]));return r.default=()=>e,__webpack_require__.d(n,r),n},__webpack_require__.d=(e,t)=>{for(var n in t)__webpack_require__.o(t,n)&&!__webpack_require__.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},__webpack_require__.f={},__webpack_require__.e=e=>Promise.all(Object.keys(__webpack_require__.f).reduce(((t,n)=>(__webpack_require__.f[n](e,t),t)),[])),__webpack_require__.u=e=>(({111:"enzyme",645:"enzyme-adapter",726:"explorer"}[e]||e)+"."+{26:"d4b48aa36b1383e317ab",111:"a8f1009fa72e3f86807e",325:"713e5e15bda3843ab320",361:"dca39c129f300a617e4f",645:"a8f1009fa72e3f86807e",726:"59c927c94d2bdb7bd844"}[e]+".js"),__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),inProgress={},dataWebpackPrefix="@freecodecamp/curriculum-helpers:",__webpack_require__.l=(e,t,n,r)=>{if(inProgress[e])inProgress[e].push(t);else{var o,i;if(void 0!==n)for(var a=document.getElementsByTagName("script"),s=0;s{o.onerror=o.onload=null,clearTimeout(l);var r=inProgress[e];if(delete inProgress[e],o.parentNode&&o.parentNode.removeChild(o),r&&r.forEach((e=>e(n))),t)return t(n)},l=setTimeout(u.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=u.bind(null,o.onerror),o.onload=u.bind(null,o.onload),i&&document.head.appendChild(o)}},__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;__webpack_require__.g.importScripts&&(e=__webpack_require__.g.location+"");var t=__webpack_require__.g.document;if(!e&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(e=t.currentScript.src),!e)){var n=t.getElementsByTagName("script");if(n.length)for(var r=n.length-1;r>-1&&(!e||!/^http(s?):/.test(e));)e=n[r--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),__webpack_require__.p=e})(),(()=>{var e={782:0};__webpack_require__.f.j=(t,n)=>{var r=__webpack_require__.o(e,t)?e[t]:void 0;if(0!==r)if(r)n.push(r[2]);else{var o=new Promise(((n,o)=>r=e[t]=[n,o]));n.push(r[2]=o);var i=__webpack_require__.p+__webpack_require__.u(t),a=new Error;__webpack_require__.l(i,(n=>{if(__webpack_require__.o(e,t)&&(0!==(r=e[t])&&(e[t]=void 0),r)){var o=n&&("load"===n.type?"missing":n.type),i=n&&n.target&&n.target.src;a.message="Loading chunk "+t+" failed.\n("+o+": "+i+")",a.name="ChunkLoadError",a.type=o,a.request=i,r[1](a)}}),"chunk-"+t,t)}};var t=(t,n)=>{var r,o,[i,a,s]=n,c=0;if(i.some((t=>0!==e[t]))){for(r in a)__webpack_require__.o(a,r)&&(__webpack_require__.m[r]=a[r]);s&&s(__webpack_require__)}for(t&&t(n);c{"use strict";var jquery__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(87569),jquery__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(jquery__WEBPACK_IMPORTED_MODULE_0__),_helpers_lib__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(92920),_sinonjs_fake_timers__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(6844),chai__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(22222),_shared_src_messages__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(83443),_shared_src_ids__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(71489),_mock_local_storage__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(57879),_shared_src_proxy_console__WEBPACK_IMPORTED_MODULE_7__=__webpack_require__(76963),_shared_src_format__WEBPACK_IMPORTED_MODULE_8__=__webpack_require__(9060),_shared_src_async_iife__WEBPACK_IMPORTED_MODULE_9__=__webpack_require__(22707),_shared_src_proxy_fetch__WEBPACK_IMPORTED_MODULE_10__=__webpack_require__(79033);const READY_MESSAGE={type:"ready"};globalThis.$=jquery__WEBPACK_IMPORTED_MODULE_0___default(),globalThis.__FakeTimers=_sinonjs_fake_timers__WEBPACK_IMPORTED_MODULE_2__,globalThis.assert=chai__WEBPACK_IMPORTED_MODULE_3__.vA,globalThis.__helpers=_helpers_lib__WEBPACK_IMPORTED_MODULE_1__,Object.defineProperty(globalThis,"localStorage",{value:new _mock_local_storage__WEBPACK_IMPORTED_MODULE_6__.Z});const removeTestScripts=()=>{const e=document.getElementById(_shared_src_ids__WEBPACK_IMPORTED_MODULE_5__.V);e?.remove();const t=document.getElementById(_shared_src_ids__WEBPACK_IMPORTED_MODULE_5__.o);t?.remove()};document.addEventListener("submit",(e=>{e.preventDefault()})),globalThis.fetch=(0,_shared_src_proxy_fetch__WEBPACK_IMPORTED_MODULE_10__.V)(parent);class DOMTestEvaluator{#a;#n;#s(e){return{err:{message:e.message,stack:e.stack,...!!e.expected&&{expected:e.expected},...!!e.actual&&{actual:e.actual}}}}constructor(e=new _shared_src_proxy_console__WEBPACK_IMPORTED_MODULE_7__.Y(globalThis.console,_shared_src_format__WEBPACK_IMPORTED_MODULE_8__.G)){this.#n=e}async init(opts){removeTestScripts();const codeObj=opts.code,code=(codeObj?.contents??"").slice(),editableContents=(codeObj?.editableContents??"").slice(),__testEditable=e=>{const t=document.createElement("div");t.id="editable-only",t.innerHTML=editableContents,document.body.appendChild(t);const n=e();return document.body.removeChild(t),n},DeepFreeze=e=>(Object.freeze(e),Object.getOwnPropertyNames(e).forEach((t=>{!Object.hasOwn(e,t)||null===e[t]||"object"!=typeof e[t]&&"function"!=typeof e[t]||Object.isFrozen(e[t])||DeepFreeze(e[t])})),e);let Enzyme;if(opts.loadEnzyme){let e;[{default:Enzyme},{default:e}]=await Promise.all([Promise.all([__webpack_require__.e(361),__webpack_require__.e(111)]).then(__webpack_require__.t.bind(__webpack_require__,71361,23)),Promise.all([__webpack_require__.e(361),__webpack_require__.e(26),__webpack_require__.e(645)]).then(__webpack_require__.t.bind(__webpack_require__,21645,23))]),Enzyme.configure({adapter:new e})}return this.#a=async function(rawTest){this.#n.on();try{const testWithBefore=`${opts.hooks?.beforeEach??""};\n${rawTest}`,test=(0,_shared_src_async_iife__WEBPACK_IMPORTED_MODULE_9__.o)(testWithBefore);return await eval(test),{pass:!0,...this.#n.flush()}}catch(e){this.#n.off(),console.error(e);const t=e;return{...this.#s(t),...this.#n.flush()}}finally{this.#n.off();try{opts.hooks?.afterEach&&eval(opts.hooks.afterEach)}catch(e){return this.#s(e)}}},Promise.resolve()}async runTest(e){return this.#a(e)}async runCode(code){try{await eval(code)}catch(e){console.error("Error evaluating code:",code,e)}}async handleMessage(e){const t=t=>e.ports[0].postMessage(t);if("test"===e.data.type){const n={type:"result",value:await this.#a(e.data.value)};(0,_shared_src_messages__WEBPACK_IMPORTED_MODULE_4__.z)(t,n)}else"init"===e.data.type?(await this.init(e.data.value),t(READY_MESSAGE)):"code"===e.data.type&&(await this.runCode(e.data.value),t({type:"code"}))}}const evaluator=new DOMTestEvaluator;onmessage=function(e){e.source===self.parent&&evaluator.handleMessage(e)}})()})(); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt b/mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt deleted file mode 100644 index cea3cf725..000000000 --- a/mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt +++ /dev/null @@ -1,550 +0,0 @@ -/*! - * ### ._obj - * - * Quick reference to stored `actual` value for plugin developers. - * - * @api private - */ - -/*! - * ### .ifError(object) - * - * Asserts if value is not a false value, and throws if it is a true value. - * This is added to allow for chai to be a drop-in replacement for Node's - * assert class. - * - * var err = new Error('I am a custom error'); - * assert.ifError(err); // Rethrows err! - * - * @name ifError - * @param {Object} object - * @namespace Assert - * @api public - */ - -/*! - * Aliases. - */ - -/*! - * Assertion Constructor - * - * Creates object for chaining. - * - * `Assertion` objects contain metadata in the form of flags. Three flags can - * be assigned during instantiation by passing arguments to this constructor: - * - * - `object`: This flag contains the target of the assertion. For example, in - * the assertion `expect(numKittens).to.equal(7);`, the `object` flag will - * contain `numKittens` so that the `equal` assertion can reference it when - * needed. - * - * - `message`: This flag contains an optional custom error message to be - * prepended to the error message that's generated by the assertion when it - * fails. - * - * - `ssfi`: This flag stands for "start stack function indicator". It - * contains a function reference that serves as the starting point for - * removing frames from the stack trace of the error that's created by the - * assertion when it fails. The goal is to provide a cleaner stack trace to - * end users by removing Chai's internal functions. Note that it only works - * in environments that support `Error.captureStackTrace`, and only when - * `Chai.config.includeStack` hasn't been set to `false`. - * - * - `lockSsfi`: This flag controls whether or not the given `ssfi` flag - * should retain its current value, even as assertions are chained off of - * this object. This is usually set to `true` when creating a new assertion - * from within another assertion. It's also temporarily set to `true` before - * an overwritten assertion gets called by the overwriting assertion. - * - * - `eql`: This flag contains the deepEqual function to be used by the assertion. - * - * @param {Mixed} obj target of the assertion - * @param {String} msg (optional) custom error message - * @param {Function} ssfi (optional) starting point for removing stack frames - * @param {Boolean} lockSsfi (optional) whether or not the ssfi flag is locked - * @api private - */ - -/*! - * Chai dependencies. - */ - -/*! - * Module dependencies. - */ - -/*! - * Module export. - */ - -/*! - * Add a chainable method - */ - -/*! - * Assert interface - */ - -/*! - * Assertion Error - */ - -/*! - * Chai - addChainingMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - addLengthGuard utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - addMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - addProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - compareByInspect utility - * Copyright(c) 2011-2016 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - expectTypes utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - flag utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - getActual utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - getOwnEnumerableProperties utility - * Copyright(c) 2011-2016 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - getOwnEnumerablePropertySymbols utility - * Copyright(c) 2011-2016 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - getProperties utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - isNaN utility - * Copyright(c) 2012-2015 Sakthipriyan Vairamani - * MIT Licensed - */ - -/*! - * Chai - isProxyEnabled helper - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - message composition utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - overwriteChainableMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - overwriteMethod utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - overwriteProperty utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - test utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai - transferFlags utility - * Copyright(c) 2012-2014 Jake Luer - * MIT Licensed - */ - -/*! - * Chai version - */ - -/*! - * Check if a property exists - */ - -/*! - * Check to see if the MemoizeMap has recorded a result of the two operands - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {MemoizeMap} memoizeMap - * @returns {Boolean|null} result -*/ - -/*! - * Checks error against a given set of criteria - */ - -/*! - * Compare by inspect method - */ - -/*! - * Compare two Regular Expressions for equality. - * - * @param {RegExp} leftHandOperand - * @param {RegExp} rightHandOperand - * @return {Boolean} result - */ - -/*! - * Compare two Sets/Maps for equality. Faster than other equality functions. - * - * @param {Set} leftHandOperand - * @param {Set} rightHandOperand - * @param {Object} [options] (Optional) - * @return {Boolean} result - */ - -/*! - * Configuration - */ - -/*! - * Core Assertions - */ - -/*! - * Deep equal utility - */ - -/*! - * Deep path info - */ - -/*! - * Dependencies that are used for multiple exports are required here only once - */ - -/*! - * Determine if the given object has an @@iterator function. - * - * @param {Object} target - * @return {Boolean} `true` if the object has an @@iterator function. - */ - -/*! - * Determines if two objects have matching values, given a set of keys. Defers to deepEqual for the equality check of - * each key. If any value of the given key is not equal, the function will return false (early). - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {Array} keys An array of keys to compare the values of leftHandOperand and rightHandOperand against - * @param {Object} [options] (Optional) - * @return {Boolean} result - */ - -/*! - * Ensure correct constructor - */ - -/*! - * Expect interface - */ - -/*! - * Flag transferring utility - */ - -/*! - * Flag utility - */ - -/*! - * Function name - */ - -/*! - * Get own enumerable properties method - */ - -/*! - * Get own enumerable property symbols method - */ - -/*! - * Gets all entries from a Generator. This will consume the generator - which could have side effects. - * - * @param {Generator} target - * @returns {Array} an array of entries from the Generator. - */ - -/*! - * Gets all iterator entries from the given Object. If the Object has no @@iterator function, returns an empty array. - * This will consume the iterator - which could have side effects depending on the @@iterator implementation. - * - * @param {Object} target - * @returns {Array} an array of entries from the @@iterator function - */ - -/*! - * Gets all own and inherited enumerable keys from a target. - * - * @param {Object} target - * @returns {Array} an array of own and inherited enumerable keys from the target. - */ - -/*! - * Inherit from Error.prototype - */ - -/*! - * Inspect util - */ - -/*! - * Module dependencies - */ - -/*! - * Object Display util - */ - -/*! - * Overwrite chainable method - */ - -/*! - * Primary Export - */ - -/*! - * Primary Exports - */ - -/*! - * Primary `Assertion` prototype - */ - -/*! - * Proxify util - */ - -/*! - * Recursively check the equality of two Objects. Once basic sameness has been established it will defer to `deepEqual` - * for each enumerable key in the object. - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {Object} [options] (Optional) - * @return {Boolean} result - */ - -/*! - * Return a function that will copy properties from - * one object to another excluding any originally - * listed. Returned function will create a new `{}`. - * - * @param {String} excluded properties ... - * @return {Function} - */ - -/*! - * Returns true if the argument is a primitive. - * - * This intentionally returns true for all objects that can be compared by reference, - * including functions and symbols. - * - * @param {Mixed} value - * @return {Boolean} result - */ - -/*! - * Set the result of the equality into the MemoizeMap - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {MemoizeMap} memoizeMap - * @param {Boolean} result -*/ - -/*! - * Should interface - */ - -/*! - * Simple equality for flat iterable objects such as Arrays, TypedArrays or Node.js buffers. - * - * @param {Iterable} leftHandOperand - * @param {Iterable} rightHandOperand - * @param {Object} [options] (Optional) - * @return {Boolean} result - */ - -/*! - * Simple equality for generator objects such as those returned by generator functions. - * - * @param {Iterable} leftHandOperand - * @param {Iterable} rightHandOperand - * @param {Object} [options] (Optional) - * @return {Boolean} result - */ - -/*! - * Statically set name - */ - -/*! - * The main logic of the `deepEqual` function. - * - * @param {Mixed} leftHandOperand - * @param {Mixed} rightHandOperand - * @param {Object} [options] (optional) Additional options - * @param {Array} [options.comparator] (optional) Override default algorithm, determining custom equality. - * @param {Array} [options.memoize] (optional) Provide a custom memoization object which will cache the results of - complex objects for a speed boost. By passing `false` you can disable memoization, but this will cause circular - references to blow the stack. - * @return {Boolean} equal match -*/ - -/*! - * Utility Functions - */ - -/*! - * Utils for plugins (not exported) - */ - -/*! - * actual utility - */ - -/*! - * add Method - */ - -/*! - * add Property - */ - -/*! - * addLengthGuard util - */ - -/*! - * assertion-error - * Copyright(c) 2013 Jake Luer - * MIT Licensed - */ - -/*! - * chai - * Copyright(c) 2011 Jake Luer - * MIT Licensed - */ - -/*! - * chai - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -/*! - * chai - * http://chaijs.com - * Copyright(c) 2011-2014 Jake Luer - * MIT Licensed - */ - -/*! - * deep-eql - * Copyright(c) 2013 Jake Luer - * MIT Licensed - */ - -/*! - * expectTypes utility - */ - -/*! - * getOperator method - */ - -/*! - * isNaN method - */ - -/*! - * isProxyEnabled helper - */ - -/*! - * jQuery JavaScript Library v3.7.1 - * https://jquery.com/ - * - * Copyright OpenJS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2023-08-28T13:37Z - */ - -/*! - * message utility - */ - -/*! - * overwrite Method - */ - -/*! - * overwrite Property - */ - -/*! - * test utility - */ - -/*! - * type utility - */ diff --git a/mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js b/mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js deleted file mode 100644 index 5811e9cbd..000000000 --- a/mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[111,645],{596(){},1784(){},22179(){},26127(){},62383(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js b/mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js deleted file mode 100644 index 5811e9cbd..000000000 --- a/mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[111,645],{596(){},1784(){},22179(){},26127(){},62383(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js b/mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js deleted file mode 100644 index dcb140c4e..000000000 --- a/mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{23(){},2329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=2329,e.exports=t},4838(){},6509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>c});var n=r(7706);function i(e){return(0,n.createSourceFile)("inline.ts",e,n.ScriptTarget.Latest,!0,n.ScriptKind.TS)}function s(e){if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,n.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,n.isInterfaceDeclaration)(e)||(0,n.isTypeLiteralNode)(e)||(0,n.isClassDeclaration)(e)?e.members:(0,n.isTypeAliasDeclaration)(e)?(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,n.isPropertySignature)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isParameter)(e))&&e.type&&(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]}function a(e,t=n.SyntaxKind.Unknown){if(!e.trim())return null;let r;return t===n.SyntaxKind.MethodDeclaration?(r=i(`class _ { ${e} }`),r.statements[0].members.find((e=>(0,n.isMethodDeclaration)(e)))||null):t===n.SyntaxKind.TypeReference?(r=i(`let _: ${e};`),r.statements[0].declarationList.declarations[0].type||null):(r=i(e),r)}const o=e=>e.filter((({kind:e})=>e!==n.SyntaxKind.SemicolonToken)),l=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,n.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const i=o(e.getChildren()),s=o(t.getChildren());if(0===i.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(i.length!==s.length)return!1;for(let e=0;et.kind===e)).map((e=>new c(e)))}getVariables(){const e=this.getAll(n.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}getAnnotation(){if(this.isEmpty())return new c;const e=this.tree;if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new c(t.type)}return((0,n.isParameter)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isPropertySignature)(e)||(0,n.isTypeAliasDeclaration)(e))&&e.type?new c(e.type):new c}hasAnnotation(e){const t=this.getAnnotation();if(t.isEmpty())return!1;const r=a(e,n.SyntaxKind.TypeReference);if(null===r)return!1;const i=new c(r);return t.matches(i)}getFunctions(e=!1){const t={};return this.getAll(n.SyntaxKind.FunctionDeclaration).forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),e&&this.getAll(n.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,n.isArrowFunction)(t.initializer)||(0,n.isFunctionExpression)(t.initializer))})).forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,n.isFunctionDeclaration)(this.tree)||(0,n.isMethodDeclaration)(this.tree)||(0,n.isArrowFunction)(this.tree)||(0,n.isFunctionExpression)(this.tree))&&(t=this.tree),(0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,n.isArrowFunction)(e)||(0,n.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new c(t.type),i=new c(e,n.SyntaxKind.TypeReference);return r.matches(i)}return!1}getParameters(){if(!this.tree)return[];if((0,n.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new c(e)));if((0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,n.isArrowFunction)(e)||e&&(0,n.isFunctionExpression)(e))return e.parameters.map((e=>new c(e)))}return[]}getTypes(){const e=this.getAll(n.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getInterfaces(){const e=this.getAll(n.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getClasses(){const e=this.getAll(n.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}getMethods(){const e={};return this.tree&&(0,n.isClassDeclaration)(this.tree)&&this.getAll(n.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}getClassProps(){if(!this.tree||!(0,n.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(n.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,n=r.name.text;e[n]=new c(r)})),e}getTypeProps(){if(!this.tree)return{};const e={};return this.getAll(n.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}hasTypeProp(e,t,r){if(!this.tree)return!1;const i=s(this.tree);if(!i)return!1;const a=i.find((t=>!(!t.name||!(0,n.isIdentifier)(t.name))&&t.name.text===e));if(!a)return!1;if(void 0!==t){if(!(0,n.isPropertySignature)(a))return!1;if(!a.type)return!1;if(!new c(a.type).matches(new c(t,n.SyntaxKind.TypeReference)))return!1}return void 0===r||!(0,n.isPropertySignature)(a)||void 0!==a.questionToken===r}hasTypeProps(e){if(!this.tree)return!1;if(!((0,n.isInterfaceDeclaration)(this.tree)||(0,n.isTypeLiteralNode)(this.tree)||(0,n.isTypeAliasDeclaration)(this.tree)||(0,n.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.getTypeProps();return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(!new c(r.tree.type).matches(new c(e.type,n.SyntaxKind.TypeReference)))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}}},7331(){},8164(){},8234(){},9905(){},9997(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js b/mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js deleted file mode 100644 index 3e4b9b154..000000000 --- a/mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{4838(){},22329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=22329,e.exports=t},28164(){},37331(){},40023(){},79997(){},88234(){},89905(){},96509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>p});var i=r(77706);function n(e){return(0,i.createSourceFile)("inline.ts",e,i.ScriptTarget.Latest,!0,i.ScriptKind.TS)}function s(e){return(0,i.isSourceFile)(e)||(0,i.isBlock)(e)||(0,i.isModuleBlock)(e)||(0,i.isCaseOrDefaultClause)(e)?e.statements:[]}function a(e){if((0,i.isFunctionDeclaration)(e)||(0,i.isMethodDeclaration)(e)||(0,i.isFunctionExpression)(e)||(0,i.isArrowFunction)(e)||(0,i.isConstructorDeclaration)(e))return e.body;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];if(t&&((0,i.isArrowFunction)(t)||(0,i.isFunctionExpression)(t)))return t.body}}const o=[["constructor",[i.isConstructorDeclaration]],["method",[i.isMethodDeclaration]],["propertyDeclaration",[i.isPropertyDeclaration]],["parameter",[i.isParameter]],["typeParameter",[i.isTypeParameterDeclaration]],["typeReference",[i.isTypeReferenceNode,i.isUnionTypeNode,i.isIntersectionTypeNode,i.isArrayTypeNode,i.isTupleTypeNode,i.isFunctionTypeNode,i.isTypeLiteralNode,i.isLiteralTypeNode,i.isParenthesizedTypeNode,i.isConditionalTypeNode,i.isMappedTypeNode,i.isTemplateLiteralTypeNode,i.isIndexedAccessTypeNode,i.isTypeOperatorNode,i.isRestTypeNode,i.isOptionalTypeNode,i.isTypeNode]],["expression",[i.isObjectLiteralExpression,i.isArrayLiteralExpression,i.isNonNullExpression,i.isAsExpression,i.isPropertyAccessExpression,i.isBinaryExpression,i.isArrowFunction,i.isFunctionExpression,i.isExpression]]];function c(e,t){if(!e.trim())return null;switch(t){case"method":return n(`class _ { ${e} }`).statements[0].members.find(i.isMethodDeclaration)??null;case"constructor":return n(`class _ { ${e} }`).statements[0].members.find(i.isConstructorDeclaration)??null;case"parameter":return n(`function _(${e}) {}`).statements[0].parameters[0]??null;case"typeParameter":{const t=n(`function _<${e}>() {}`).statements[0];return t.typeParameters?.[0]??null}case"propertyDeclaration":return n(`class _ { ${e} }`).statements[0].members.find(i.isPropertyDeclaration)??null;case"typeReference":return n(`let _: ${e};`).statements[0].declarationList.declarations[0].type??null;case"expression":return n(`const _ = ${e};`).statements[0].declarationList.declarations[0].initializer??null;case"source":return n(e);default:return null}}const l=e=>e.filter((({kind:e})=>e!==i.SyntaxKind.SemicolonToken)),u=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,i.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const n=l(e.getChildren()),s=l(t.getChildren());if(0===n.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(n.length!==s.length)return!1;for(let e=0;et(e))))return t;return"source"}(e))}isEmpty(){return null===this.tree}toString(){return this.tree?this.tree.getText():"no ast"}matches(e){return"string"==typeof e?!this.tree&&!e.trim()||!!this.tree&&u(this.tree,c(e,this.context)):u(this.tree,e.tree)}getAll(e){if(!this.tree)return[];const t=a(this.tree)??this.tree;return[...function(e){if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,i.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,i.isInterfaceDeclaration)(e)||(0,i.isTypeLiteralNode)(e)||(0,i.isClassDeclaration)(e)?e.members:(0,i.isTypeAliasDeclaration)(e)?(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,i.isPropertySignature)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e))&&e.type&&(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]}(t),...s(t)].filter((t=>t.kind===e)).map((e=>new p(e)))}get variables(){const e=this.getAll(i.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}get value(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];return t?new p(t):new p}return(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e)?e.initializer?new p(e.initializer):new p:(0,i.isPropertyAssignment)(e)&&e.initializer?new p(e.initializer):new p}get annotation(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new p(t.type)}return((0,i.isParameter)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isPropertySignature)(e)||(0,i.isTypeAliasDeclaration)(e))&&e.type?new p(e.type):new p}isUnionOf(e){const t=this.annotation;if(t.isEmpty())return!1;const r=t.tree;if(!r||!(0,i.isUnionTypeNode)(r))return!1;const n=r.types;return n.length===e.length&&n.every((t=>e.some((e=>{const r=new p(e,"typeReference");return!r.isEmpty()&&u(t,r.tree)}))))}hasAnnotation(e){const t=this.annotation;if(t.isEmpty())return!1;const r=new p(e,"typeReference");return t.matches(r)}get functions(){const e={};return this.getAll(i.SyntaxKind.FunctionDeclaration).forEach((t=>{const r=t.tree.name?.text;r&&(e[r]=t)})),e}get allFunctions(){const e={...this.functions};return this.getAll(i.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,i.isArrowFunction)(t.initializer)||(0,i.isFunctionExpression)(t.initializer))})).forEach((t=>{const r=t.tree.declarationList.declarations[0].name.text;e[r]=t})),e}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))&&(t=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new p(t.type),i=new p(e,"typeReference");return r.matches(i)}return!1}get parameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new p(e)));if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,i.isArrowFunction)(e)||e&&(0,i.isFunctionExpression)(e))return e.parameters.map((e=>new p(e)))}return(0,i.isMethodDeclaration)(this.tree)?this.tree.parameters.map((e=>new p(e))):[]}get typeParameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isClassDeclaration)(this.tree)||(0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e)))return e.typeParameters?.map((e=>new p(e)))??[]}return[]}get typeArguments(){return this.tree&&((0,i.isTypeReferenceNode)(this.tree)||(0,i.isCallExpression)(this.tree)||(0,i.isNewExpression)(this.tree))?this.tree.typeArguments?.map((e=>new p(e)))??[]:[]}hasReturn(e){if(this.isEmpty())return!1;const t=a(this.tree);if(!t)return!1;if((0,i.isBlock)(t)){const r=s(t);for(const t of r)if(t.kind===i.SyntaxKind.ReturnStatement){const r=t;if(r.expression)return new p(r.expression).matches(e)}return!1}return new p(t).matches(e)}get types(){const e=this.getAll(i.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get interfaces(){const e=this.getAll(i.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get classes(){const e=this.getAll(i.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}get methods(){const e={};return this.tree&&(0,i.isClassDeclaration)(this.tree)&&this.getAll(i.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get classConstructor(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return null;const e=this.getAll(i.SyntaxKind.Constructor);return e.length>0?e[0]:null}get classProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(i.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,i=r.name.text;e[i]=new p(r)})),e}get constructorProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e=this.classConstructor;if(!e||e.isEmpty())return{};const t=a(e.tree);if(!t||!(0,i.isBlock)(t))return{};const r={};return t.statements.forEach((e=>{if((0,i.isExpressionStatement)(e)){const t=e.expression;if((0,i.isBinaryExpression)(t)&&(0,i.isPropertyAccessExpression)(t.left)){const n=t.left;if(n.expression.kind===i.SyntaxKind.ThisKeyword&&(0,i.isIdentifier)(n.name)){const t=n.name.text;t in r||(r[t]=new p(e))}}}})),r}get typeProps(){if(!this.tree)return{};const e={};return this.getAll(i.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get objectProps(){if(!this.tree)return{};let e;if((0,i.isObjectLiteralExpression)(this.tree)&&(e=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:t}=this.tree.declarationList.declarations[0];t&&(0,i.isObjectLiteralExpression)(t)&&(e=t)}if(!e)return{};const t={};return e.properties.forEach((e=>{if((0,i.isPropertyAssignment)(e)&&e.name&&(0,i.isIdentifier)(e.name)){const r=e.name.text;t[r]=new p(e)}})),t}hasTypeProps(e){if(!this.tree)return!1;if(!((0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeLiteralNode)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree)||(0,i.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.typeProps;return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(!new p(r.tree.type).matches(new p(e.type,"typeReference")))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}hasCast(e){if(this.isEmpty())return!1;const t=this.tree;if((0,i.isAsExpression)(t)){if(void 0===e)return!0;const r=t.type;if(r){const t=new p(r),i=new p(e,"typeReference");return t.matches(i)}return!1}return!1}doesExtend(e){if(!this.tree)return!1;const t=this.tree,r=((0,i.isClassDeclaration)(t)||(0,i.isInterfaceDeclaration)(t))&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ExtendsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}doesImplement(e){if(!this.tree)return!1;const t=this.tree,r=(0,i.isClassDeclaration)(t)&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ImplementsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}isPrivate(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PrivateKeyword))??!1)}isProtected(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ProtectedKeyword))??!1)}isPublic(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PublicKeyword))??!1)}isReadOnly(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ReadonlyKeyword))??!1)}hasNonNullAssertion(){return!!this.tree&&(0,i.isNonNullExpression)(this.tree)}}}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js b/mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js deleted file mode 100644 index e21d43ebd..000000000 --- a/mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{23(){},2329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=2329,e.exports=t},4838(){},6509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>p});var i=r(7706);function n(e){return(0,i.createSourceFile)("inline.ts",e,i.ScriptTarget.Latest,!0,i.ScriptKind.TS)}function s(e){return(0,i.isSourceFile)(e)||(0,i.isBlock)(e)||(0,i.isModuleBlock)(e)||(0,i.isCaseOrDefaultClause)(e)?e.statements:[]}function a(e){if((0,i.isFunctionDeclaration)(e)||(0,i.isMethodDeclaration)(e)||(0,i.isFunctionExpression)(e)||(0,i.isArrowFunction)(e)||(0,i.isConstructorDeclaration)(e))return e.body;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];if(t&&((0,i.isArrowFunction)(t)||(0,i.isFunctionExpression)(t)))return t.body}}const o=[["constructor",[i.isConstructorDeclaration]],["method",[i.isMethodDeclaration]],["propertyDeclaration",[i.isPropertyDeclaration]],["parameter",[i.isParameter]],["typeParameter",[i.isTypeParameterDeclaration]],["typeReference",[i.isTypeReferenceNode,i.isUnionTypeNode,i.isIntersectionTypeNode,i.isArrayTypeNode,i.isTupleTypeNode,i.isFunctionTypeNode,i.isTypeLiteralNode,i.isLiteralTypeNode,i.isParenthesizedTypeNode,i.isConditionalTypeNode,i.isMappedTypeNode,i.isTemplateLiteralTypeNode,i.isIndexedAccessTypeNode,i.isTypeOperatorNode,i.isRestTypeNode,i.isOptionalTypeNode,i.isTypeNode]],["expression",[i.isObjectLiteralExpression,i.isArrayLiteralExpression,i.isNonNullExpression,i.isAsExpression,i.isPropertyAccessExpression,i.isBinaryExpression,i.isArrowFunction,i.isFunctionExpression,i.isExpression]]];function c(e,t){if(!e.trim())return null;switch(t){case"method":return n(`class _ { ${e} }`).statements[0].members.find(i.isMethodDeclaration)??null;case"constructor":return n(`class _ { ${e} }`).statements[0].members.find(i.isConstructorDeclaration)??null;case"parameter":return n(`function _(${e}) {}`).statements[0].parameters[0]??null;case"typeParameter":{const t=n(`function _<${e}>() {}`).statements[0];return t.typeParameters?.[0]??null}case"propertyDeclaration":return n(`class _ { ${e} }`).statements[0].members.find(i.isPropertyDeclaration)??null;case"typeReference":return n(`let _: ${e};`).statements[0].declarationList.declarations[0].type??null;case"expression":return n(`const _ = ${e};`).statements[0].declarationList.declarations[0].initializer??null;case"source":return n(e);default:return null}}const l=e=>e.filter((({kind:e})=>e!==i.SyntaxKind.SemicolonToken)),u=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,i.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const n=l(e.getChildren()),s=l(t.getChildren());if(0===n.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(n.length!==s.length)return!1;for(let e=0;et(e))))return t;return"source"}(e))}isEmpty(){return null===this.tree}toString(){return this.tree?this.tree.getText():"no ast"}matches(e){return"string"==typeof e?!this.tree&&!e.trim()||!!this.tree&&u(this.tree,c(e,this.context)):u(this.tree,e.tree)}getAll(e){if(!this.tree)return[];const t=a(this.tree)??this.tree;return[...function(e){if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,i.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,i.isInterfaceDeclaration)(e)||(0,i.isTypeLiteralNode)(e)||(0,i.isClassDeclaration)(e)?e.members:(0,i.isTypeAliasDeclaration)(e)?(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,i.isPropertySignature)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e))&&e.type&&(0,i.isTypeLiteralNode)(e.type)?e.type.members:[]}(t),...s(t)].filter((t=>t.kind===e)).map((e=>new p(e)))}get variables(){const e=this.getAll(i.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}get value(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const{initializer:t}=e.declarationList.declarations[0];return t?new p(t):new p}return(0,i.isPropertyDeclaration)(e)||(0,i.isParameter)(e)?e.initializer?new p(e.initializer):new p:(0,i.isPropertyAssignment)(e)&&e.initializer?new p(e.initializer):new p}get annotation(){if(this.isEmpty())return new p;const e=this.tree;if((0,i.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new p(t.type)}return((0,i.isParameter)(e)||(0,i.isPropertyDeclaration)(e)||(0,i.isPropertySignature)(e)||(0,i.isTypeAliasDeclaration)(e))&&e.type?new p(e.type):new p}isUnionOf(e){const t=this.annotation;if(t.isEmpty())return!1;const r=t.tree;if(!r||!(0,i.isUnionTypeNode)(r))return!1;const n=r.types;return n.length===e.length&&n.every((t=>e.some((e=>{const r=new p(e,"typeReference");return!r.isEmpty()&&u(t,r.tree)}))))}hasAnnotation(e){const t=this.annotation;if(t.isEmpty())return!1;const r=new p(e,"typeReference");return t.matches(r)}get functions(){const e={};return this.getAll(i.SyntaxKind.FunctionDeclaration).forEach((t=>{const r=t.tree.name?.text;r&&(e[r]=t)})),e}get allFunctions(){const e={...this.functions};return this.getAll(i.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,i.isArrowFunction)(t.initializer)||(0,i.isFunctionExpression)(t.initializer))})).forEach((t=>{const r=t.tree.declarationList.declarations[0].name.text;e[r]=t})),e}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))&&(t=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new p(t.type),i=new p(e,"typeReference");return r.matches(i)}return!1}get parameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new p(e)));if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,i.isArrowFunction)(e)||e&&(0,i.isFunctionExpression)(e))return e.parameters.map((e=>new p(e)))}return(0,i.isMethodDeclaration)(this.tree)?this.tree.parameters.map((e=>new p(e))):[]}get typeParameters(){if(!this.tree)return[];if((0,i.isFunctionDeclaration)(this.tree)||(0,i.isMethodDeclaration)(this.tree)||(0,i.isClassDeclaration)(this.tree)||(0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isArrowFunction)(this.tree)||(0,i.isFunctionExpression)(this.tree))return this.tree.typeParameters?.map((e=>new p(e)))??[];if((0,i.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&((0,i.isArrowFunction)(e)||(0,i.isFunctionExpression)(e)))return e.typeParameters?.map((e=>new p(e)))??[]}return[]}get typeArguments(){return this.tree&&((0,i.isTypeReferenceNode)(this.tree)||(0,i.isCallExpression)(this.tree)||(0,i.isNewExpression)(this.tree))?this.tree.typeArguments?.map((e=>new p(e)))??[]:[]}hasReturn(e){if(this.isEmpty())return!1;const t=a(this.tree);if(!t)return!1;if((0,i.isBlock)(t)){const r=s(t);for(const t of r)if(t.kind===i.SyntaxKind.ReturnStatement){const r=t;if(r.expression)return new p(r.expression).matches(e)}return!1}return new p(t).matches(e)}get types(){const e=this.getAll(i.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get interfaces(){const e=this.getAll(i.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}get classes(){const e=this.getAll(i.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}get methods(){const e={};return this.tree&&(0,i.isClassDeclaration)(this.tree)&&this.getAll(i.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get classConstructor(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return null;const e=this.getAll(i.SyntaxKind.Constructor);return e.length>0?e[0]:null}get classProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(i.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,i=r.name.text;e[i]=new p(r)})),e}get constructorProps(){if(!this.tree||!(0,i.isClassDeclaration)(this.tree))return{};const e=this.classConstructor;if(!e||e.isEmpty())return{};const t=a(e.tree);if(!t||!(0,i.isBlock)(t))return{};const r={};return t.statements.forEach((e=>{if((0,i.isExpressionStatement)(e)){const t=e.expression;if((0,i.isBinaryExpression)(t)&&(0,i.isPropertyAccessExpression)(t.left)){const n=t.left;if(n.expression.kind===i.SyntaxKind.ThisKeyword&&(0,i.isIdentifier)(n.name)){const t=n.name.text;t in r||(r[t]=new p(e))}}}})),r}get typeProps(){if(!this.tree)return{};const e={};return this.getAll(i.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}get objectProps(){if(!this.tree)return{};let e;if((0,i.isObjectLiteralExpression)(this.tree)&&(e=this.tree),(0,i.isVariableStatement)(this.tree)){const{initializer:t}=this.tree.declarationList.declarations[0];t&&(0,i.isObjectLiteralExpression)(t)&&(e=t)}if(!e)return{};const t={};return e.properties.forEach((e=>{if((0,i.isPropertyAssignment)(e)&&e.name&&(0,i.isIdentifier)(e.name)){const r=e.name.text;t[r]=new p(e)}})),t}hasTypeProps(e){if(!this.tree)return!1;if(!((0,i.isInterfaceDeclaration)(this.tree)||(0,i.isTypeLiteralNode)(this.tree)||(0,i.isTypeAliasDeclaration)(this.tree)||(0,i.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.typeProps;return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(!new p(r.tree.type).matches(new p(e.type,"typeReference")))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,i.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}hasCast(e){if(this.isEmpty())return!1;const t=this.tree;if((0,i.isAsExpression)(t)){if(void 0===e)return!0;const r=t.type;if(r){const t=new p(r),i=new p(e,"typeReference");return t.matches(i)}return!1}return!1}doesExtend(e){if(!this.tree)return!1;const t=this.tree,r=((0,i.isClassDeclaration)(t)||(0,i.isInterfaceDeclaration)(t))&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ExtendsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}doesImplement(e){if(!this.tree)return!1;const t=this.tree,r=(0,i.isClassDeclaration)(t)&&t.heritageClauses?t.heritageClauses:null;if(!r)return!1;Array.isArray(e)||(e=[e]);for(const t of r){if(t.token!==i.SyntaxKind.ImplementsKeyword)continue;const r=t.types.reduce(((e,t)=>((0,i.isIdentifier)(t.expression)?e.push(t.expression.text):t.expression.kind===i.SyntaxKind.QualifiedName&&e.push(t.expression.getText()),e)),[]);if(e.every((e=>r.includes(e))))return!0}return!1}isPrivate(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PrivateKeyword))??!1)}isProtected(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ProtectedKeyword))??!1)}isPublic(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.PublicKeyword))??!1)}isReadOnly(){return!!this.tree&&(this.tree.modifiers?.some((e=>e.kind===i.SyntaxKind.ReadonlyKeyword))??!1)}hasNonNullAssertion(){return!!this.tree&&(0,i.isNonNullExpression)(this.tree)}}},7331(){},8164(){},8234(){},9905(){},9997(){}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js b/mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js deleted file mode 100644 index 947a91a19..000000000 --- a/mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_freecodecamp_curriculum_helpers=self.webpackChunk_freecodecamp_curriculum_helpers||[]).push([[726],{4838(){},22329(e){function t(e){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}t.keys=()=>[],t.resolve=t,t.id=22329,e.exports=t},28164(){},37331(){},40023(){},79997(){},88234(){},89905(){},96509(e,t,r){"use strict";r.r(t),r.d(t,{Explorer:()=>c});var n=r(77706);function i(e){return(0,n.createSourceFile)("inline.ts",e,n.ScriptTarget.Latest,!0,n.ScriptKind.TS)}function s(e){if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];return t.type&&(0,n.isTypeLiteralNode)(t.type)?t.type.members:[]}return(0,n.isInterfaceDeclaration)(e)||(0,n.isTypeLiteralNode)(e)||(0,n.isClassDeclaration)(e)?e.members:(0,n.isTypeAliasDeclaration)(e)?(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]:((0,n.isPropertySignature)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isParameter)(e))&&e.type&&(0,n.isTypeLiteralNode)(e.type)?e.type.members:[]}function a(e,t=n.SyntaxKind.Unknown){if(!e.trim())return null;let r;return t===n.SyntaxKind.MethodDeclaration?(r=i(`class _ { ${e} }`),r.statements[0].members.find((e=>(0,n.isMethodDeclaration)(e)))||null):t===n.SyntaxKind.TypeReference?(r=i(`let _: ${e};`),r.statements[0].declarationList.declarations[0].type||null):(r=i(e),r)}const o=e=>e.filter((({kind:e})=>e!==n.SyntaxKind.SemicolonToken)),l=(e,t)=>{if(null===e&&null===t)return!0;if(null===e||null===t)return!1;const r=e=>(0,n.isSourceFile)(e)&&1===e.statements.length?e.statements[0]:e;if(e=r(e),t=r(t),e.kind!==t.kind)return!1;const i=o(e.getChildren()),s=o(t.getChildren());if(0===i.length&&0===s.length)return"text"in e&&"text"in t?e.text===t.text:e.getText()===t.getText();if(i.length!==s.length)return!1;for(let e=0;et.kind===e)).map((e=>new c(e)))}getVariables(){const e=this.getAll(n.SyntaxKind.VariableStatement),t={};return e.forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}getAnnotation(){if(this.isEmpty())return new c;const e=this.tree;if((0,n.isVariableStatement)(e)){const t=e.declarationList.declarations[0];if(t.type)return new c(t.type)}return((0,n.isParameter)(e)||(0,n.isPropertyDeclaration)(e)||(0,n.isPropertySignature)(e)||(0,n.isTypeAliasDeclaration)(e))&&e.type?new c(e.type):new c}hasAnnotation(e){const t=this.getAnnotation();if(t.isEmpty())return!1;const r=a(e,n.SyntaxKind.TypeReference);if(null===r)return!1;const i=new c(r);return t.matches(i)}getFunctions(e=!1){const t={};return this.getAll(n.SyntaxKind.FunctionDeclaration).forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),e&&this.getAll(n.SyntaxKind.VariableStatement).filter((e=>{const t=e.tree.declarationList.declarations[0];return!!t.initializer&&((0,n.isArrowFunction)(t.initializer)||(0,n.isFunctionExpression)(t.initializer))})).forEach((e=>{const r=e.tree.declarationList.declarations[0].name.text;t[r]=e})),t}hasReturnAnnotation(e){if(!this.tree)return!1;let t=null;if(((0,n.isFunctionDeclaration)(this.tree)||(0,n.isMethodDeclaration)(this.tree)||(0,n.isArrowFunction)(this.tree)||(0,n.isFunctionExpression)(this.tree))&&(t=this.tree),(0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];e&&((0,n.isArrowFunction)(e)||(0,n.isFunctionExpression)(e))&&(t=e)}if(t?.type){const r=new c(t.type),i=new c(e,n.SyntaxKind.TypeReference);return r.matches(i)}return!1}getParameters(){if(!this.tree)return[];if((0,n.isFunctionDeclaration)(this.tree))return this.tree.parameters.map((e=>new c(e)));if((0,n.isVariableStatement)(this.tree)){const{initializer:e}=this.tree.declarationList.declarations[0];if(e&&(0,n.isArrowFunction)(e)||e&&(0,n.isFunctionExpression)(e))return e.parameters.map((e=>new c(e)))}return[]}getTypes(){const e=this.getAll(n.SyntaxKind.TypeAliasDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getInterfaces(){const e=this.getAll(n.SyntaxKind.InterfaceDeclaration),t={};return e.forEach((e=>{const r=e.tree.name.text;t[r]=e})),t}getClasses(){const e=this.getAll(n.SyntaxKind.ClassDeclaration),t={};return e.forEach((e=>{const r=e.tree.name?.text;r&&(t[r]=e)})),t}getMethods(){const e={};return this.tree&&(0,n.isClassDeclaration)(this.tree)&&this.getAll(n.SyntaxKind.MethodDeclaration).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}getClassProps(){if(!this.tree||!(0,n.isClassDeclaration)(this.tree))return{};const e={};return this.getAll(n.SyntaxKind.PropertyDeclaration).forEach((t=>{const r=t.tree,n=r.name.text;e[n]=new c(r)})),e}getTypeProps(){if(!this.tree)return{};const e={};return this.getAll(n.SyntaxKind.PropertySignature).forEach((t=>{const r=t.tree.name.text;e[r]=t})),e}hasTypeProp(e,t,r){if(!this.tree)return!1;const i=s(this.tree);if(!i)return!1;const a=i.find((t=>!(!t.name||!(0,n.isIdentifier)(t.name))&&t.name.text===e));if(!a)return!1;if(void 0!==t){if(!(0,n.isPropertySignature)(a))return!1;if(!a.type)return!1;if(!new c(a.type).matches(new c(t,n.SyntaxKind.TypeReference)))return!1}return void 0===r||!(0,n.isPropertySignature)(a)||void 0!==a.questionToken===r}hasTypeProps(e){if(!this.tree)return!1;if(!((0,n.isInterfaceDeclaration)(this.tree)||(0,n.isTypeLiteralNode)(this.tree)||(0,n.isTypeAliasDeclaration)(this.tree)||(0,n.isVariableStatement)(this.tree)))return!1;Array.isArray(e)||(e=[e]);const t=this.getTypeProps();return 0!==Object.keys(t).length&&0!==e.length&&e.every((function(e){const r=t[e.name];if(!r)return!1;if(void 0!==e.type){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(!new c(r.tree.type).matches(new c(e.type,n.SyntaxKind.TypeReference)))return!1}if(void 0!==e.isOptional){if(!r.tree||!(0,n.isPropertySignature)(r.tree))return!1;if(void 0!==r.tree.questionToken!==e.isOptional)return!1}return!0}))}}}}]); \ No newline at end of file diff --git a/mobile-app-new/assets/test_runner/dist/index.js b/mobile-app-new/assets/test_runner/dist/index.js deleted file mode 100644 index fb91fadbe..000000000 --- a/mobile-app-new/assets/test_runner/dist/index.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{"use strict";function t({messenger:t,message:e}){return new Promise((s=>{const r=new MessageChannel;r.port1.onmessage=t=>{r.port1.close(),s(t.data)},t.postMessage(e,{targetOrigin:"*",transfer:[r.port2]})}))}const e=(t="/dist/")=>{const e=t.startsWith("/"),s=t.endsWith("/");return e||(t="/"+t),s||(t+="/"),t},s=t=>{if("fetch"===t.data.type){const{url:e,options:s}=t.data;fetch(e,{...s,credentials:"omit"}).then((async e=>{const s=await e.text();t.ports[0].postMessage({status:e.status,statusText:e.statusText,url:e.url,text:s})}))}};class r{#t;#e;#s;#r({assetPath:t,script:s}){const r=document.createElement("iframe");return r.sandbox.add("allow-scripts","allow-forms"),r.allow="autoplay",r.id="test-frame",{iframe:r,scriptHTML:`

1^R3e-RUEUGWVH+pRdl-x1&Z)W41}Ct~;o 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:lU7k z<9{mde)<$+aY21!fKqzii$oR*@)KcE`>@pEG44~FlNJ0A_&rpeX%kV9Fn07Gfw$-E zoZA-jMzbvPV&KgG&B}QA`1nh2u4Dx{k^1ON+I%w;`}iCk zatOhAwYBUdmo0=zXqs9$4vPS;)Z)qti)4+YZb5=)_8W(Y!(;CKdUap_+LzZZEhM#I zLx9O%MO2d}b_W*rp);xVMr={W66xcYk7CGI#`dB3z&?i~T+79n=2Gipu~gVDUwV;Ntv;+SjA2 z16f3t;8y~5(*P>D2Z7D=nn}Tp2zmi*ehTdAVv6Cs!E~+CgR8Ij0gIMtm>x)o zAho7MobaL<=zEl%#Y_w_%QkgD^w|0LR(oc!ZL6x8T?Uw8)|dbmljN$vq(>Er%NhdE zjT4Yl*ZA>Ritv&B3UG^yKBFk;L`U29%J!{cmg#pXg1MI;<%{N7QBN$JC_@ zbTzG`@f#|h%mB8o+&lB59HTlr$FyYFW6afAoouU>H+Wp>g@`qcp<|e1UW^@g#om8y zXXl3%L+Ss7{Qe9^3%5*|wtAyjE;N4Y%VK|wQX9a{{S6Ap2{!s#BN;iu7xmgZ47BnT zlw>CDdsgy(Wnr?qK;mk=&OKD}@BY72a`7`0({dt~7^I`p^Jw-BNZqVSM&2l%eqU;Hc_+v{zx*eBpJ2$t z0q>mcvAD(z9~SY<#}^ARVsuO4oJ^45KIEXL%yIm&Rzc*{e8O7UAj5N$Qt3# z!bqo4I7TV2`FGw>Abf> zDZ@;v`Q+j7kx}%x`$R^1T>)1F$6@C$NAN5Ki~Iwz1mHhh8CCWOPibUR)a1m5-$t*i zzq!Nn^Yh{bO(=)EsOjA~ozJ~H_37gy`DM$$_lYT)E1|C*heAzzh@0%(a`@LS`Xsi` zWPyb$y@EZsvCl{FEPI_(C@PcTNUlg#Bh$K#PVpOt`=3d?F@ID+SydseXe>?kjILC% zTON|^)(G)~K$_}&%cb@x87d#zikDCqiWhB#r79Q?NWXXbR$t#T!KJq4t~}=80UYA) zM{41{>GjVDO+kK*|DW&YF?EEUB?h8k>p@;o2>4hLvE?J1y(r@@`^*C@`iC(WZU|=M z^D=keCPz;a)CzmzY=OY&O%dMBS037O*j(Jz5_3vdrPx~)!%fmlTrc+eoYI0NKJ5XercA^_hM7e$&)j)|*f>g!`+taFKVW z4I6K93B|E6zR)I~{sUo*m3K4*EAjbY!$o>ua{VrKSd4?5tc%vEr&;Slw1UpDZK&QL zzAONnpHwP1ledN;{|SpBOP@T}0&4Zc)v(GQZ#Ivc$CTS+*7C7_cl2aMqrv`^9pn_| zxVF}k=M}6D-7M)xlOp5^cP7@gqlU+h zm}}vcy-~Lfb7_rzxc~S4TiYL$z<*`sI4RHH5mbhc^I`dFTQDTix^#x$JfpyV3+*L1 zFUg?N)fY67vhWE&W0n{ygKloetWGQdI!WZPM2)yiN$-4x%!uBJSWpMX&Fly84; z9KjX7A&YrMdOA7sIbtP8g%eBsDE~D>Zx8VSz+}=8z#!Lxi$#-s5u8@^bHR=odl?IZ zgYzT%k3A1gY)LW{tNnWyaui1W6b(&0pLzMeNckWFJ8&VmQ;WT5t7uvch+3L*;KAYV zi&I}48!G(yh3Z6DjH6%!Wd(?fg=ppYtt%G{!!-iJjLiYpIwY3NNE&$_$#|X+CQ>jr zV!GHfJU?y}PUH8FvFYLZ`qu=boT>r8v7Om7vvF-ZEg9>=Vz!a@h`*(gFdjMPU_!$k{{26b&_RkL zIcf@vUAqX5ULt2%M)c}7wgD7-U)G1Dzjy}2&sh|7CcvWt+5WL>8RK!r44)_~C+s11 zazLl$86iNsmXfdA=$YWpBk61*sHV=b%5Xry(Ls+^4Li%QNZ?|ijFM&z#{-#OE0=w1 zYFyr8YInAHuT-{6?Spq~sXk{YGCDPX{rt$1>%VkiVW@zuQ47V)TiIkai1{9*s`Y2m zCcB{>;=OR8aD&}s<53{DDpC9>XrUz*1$F9r)rGdOPXxER5+f_a1{H)0IMPP_3(mr6 z{yN|N`q#?aJo5A53ZcK3^SxzH*cqO;(SvBpV{=l)Tej*)nYP4jB*;p4%g8mpd8;1( z*LsHnBkj)@N|zjAR}JV${o(pA9jK|`7ehZ$g>ZD&nEI0)co2$UH|0&DeY}8jz^P602`FVMcKYR zx{XDYxD{sq!|kBXQ6+ZBx9V7e+?nazGR#a|sTAbo%kTl=^Qbo8bXO#zM?# z;+X>FNXgl+uT_6}XPDS$Apf3o3L(PHH65ulR>_G{*4P6eqAbz?UX6 zN_}}=N5x$pUDf-qZ;#2+|0tkib&@w9O4Y$+xq}*cxXRHGh$T;b}U26B^O!U=X~|3{16&Wp&nc#vl@@klufTRlovMg1GeqN*12jHH|P-Sw!jj>hXL@Wz0dId{6LPb|#(ATWqtBc|Drfn`@Bq=S|_xG}k zW8QVSfPstQYYUQmBgogO-&!$=OKz z0R?B&PJJ#emYEj+{!7TfCpxmx`dtx1G2Ta2nEfp$NHRW>t8NhQ6Z1uaXZrDq*Fmpm zz5*T|SP}}?QG2yGX--p{ppVXI%#?iYVlNijRl$mzw!p*=ihTK+MO5Kgk7qL4H8$VY z@q?NTf)@|?(091XkbO*o(1)Kc_t44F+tOy%^Z! zaXYw-1LVm(|D*-Yz8h!RMi~oTCU=!327<1mdX=1iwd=Vbzz>ocTYb7&_O8Tkiqr>e ztV^)aFd4b6_zx06;v(d^6j?W|UK$Q|M9*sgpBy!^`UEQE(bCi_CjAvL30+DH!V5Bz zzS7w3DD;8%SF?J^=#Rxkku78KM|Oc42SM0E5B+RP3GCwkTiq-lt)nGYWe3HozSs;g zBZ7|4C|~|aM-7qM!=Fnxf(?f|?}#tg5h8Z?z4XK@|IoYyi`MP65qb=oXQDWL(l%H$ z9Jxh3%j#0wR@Q~H%j&@41U9LQ(a?R`Xe)qA>-%%(My}Pl9QT{3hRZXZ5=^;ZLC!SUUx>1cIdea4R`7K-5ZZDz^-UhM~vHLNC$o;5~_3M)~ z=GO3EIaa+8gV);%F;anN!cG%Fv@7AV=4gzM5ycn-!7T2mW4EmpaHDo7=U!!1KzEiVJA)-Ky#>GtyMV%${oEz=gnMW&fCp(KbGBT-+g^hPP?S@LtZgCqm-J^WMPQ|^!K+JIQt%vV zBc@e2tLJ0WV^>LK=*!Wm2t_mvzK^FZjKpwYha9oF``?=J8GP)eLT{rBQqZN+{&hAp za}Fbf^zj>TNS5>Q4e$&-k>evlJde9jz&G=6PE?Y>p}0PcR)%p8pvTys@!O+;uLqgANw>r{qCFww4Y$`%{l_-#drO^c_L(H^&{%4fcRzrsI-MG?P_ zlwie@!phd@(?l-;R|o%NeZ`ggOgQ-8sr?krSRq`eCA7`=X#!RN6@O#@booj6%H$i? zP!~5xs>2@5f1r;@mF56jpDnmRHEX6-Je zI+&EwR*(OZhC{&4ok;*UfPx2M5~&cFf%68awDRtYlL>z8R2QEbg~E5v!`rI`}8X)%24Lw79t4P0hSPanECk)(xr6qHpQszuTvap z))vPehp2!f3{Cj4(M3G&lw z>yFRfs7wQ(V3wRl*Z9;0iUqdFg#&dt%-Py0Af8Ni$smI3EoHshU_1cPSjZy_$3R65 zw6JhuGYfl}f5iw*{E;OT?s`lFWBdTkp2ke1V(3P$(IKE6%41p{{of=~!~P_H7A_N2 zL|8^FLKskG`kh@s7D$7W^iJnm1|82)vsoQn9$ZNw3+4T4$y?JPOaAlllBQ6HYz)q11jdsk8S66Z^|$W6{I zkvEc&nUy1G8t7)MBaW?rXEA(b(l9Tf=#oeF4r{X7Xv?3u&pu~j%}ure&as=-zar^` z-`virV^x|bV>nh6`4()7@n376dVl=c?()LAMFjuacaOVV?j2)2RtiNYu(z6Q4e4$&#~12iD*D5|lqmY(Ue zn0lcYU3)l+3fHp)53AgYSk&%r1i{skA=R!Y^`)Cwq10z&w%=O0M+AjbXC%Ub$=cS+ z62l_Qe<_dRXtD3;TMUnA4W5b$_*x+>nK>gPonowWH5C8J3tr}gC7{1%b|C;1@0wr_ zGh%6~?ax)x(!eLOmzaCCRzc(pJk`(Kbk4TRUObm$^(UlqlY1}Z|4~*u`V-MNTZw4J zv$S-}kJB9+!ypUfgNqR2ka~)VuZ5V8Q6%jZlTkO!P62iCd(?v05sB)8lN$((;5lx| z!d(Z!mzWJ{-JuaE>pVWmn;tju-uV7MG>alR`p5$AEYTfXHs87Y|7sw|y;-9#s>RT3 z;nO^qUWv26`U{P!=oZ7=7d7}(0H4i!wam~C05I{cnxyN&XS-lrm?vQtV!f-t@CRg4 z_-(gI!K6}-ppfHWYka+Pt?SlumE*<6gMnxEWKDj<^*-6{Ck{-+6|(i&Vs0ExzhLkhaelh(Oo0L%$Wrf#msXLZHlzl$6oYGM zkg}#cwG2tX%r}Z4Xhb{;#w(xw(6p9*l}~E1^(`p92@*#!9awjuo%2(H(etffy`JDG z#`uP6k00+jO%Fw#p&L&?LGxEd(;FgP_#W=p3=@%2OS@(e3a7L=KY3ks1qPfKXqXfD z;FQELs;+EYnCCJemOe?+`quUcFV!z5RYQ!{-q)qd%HE{IDY2qZ-CJ2W{>Cfm_nf8U zbK!#>aPK}RhoTU(WoB`vW@KBC&vnW9$yTiWC$rWQ$kE*u$v%41aemDAS-9C@;lqb- zRPTBfwqM^-N}^@KM7}oMB`li+vXi^5n;tfzwGU~>g3rl=#KlY*7Ewhp0x8-N)O(BX z=0uCt4F92B4<1utr>!`sjl=;WKff>Yl=3Q~MxMLcr2fPd8JCE>4|=+#IiGrg?fe!L z=x7L~@Et}W2*96`to>Q^FyK``HJ<$kM>+6kT5C@EPT|`5gD7;KUoleciNxp~T6b;u zjH87IW%V2o;OimwNGohzmq@}urxQS1kO&;03K7b3ioCfi;fJq*eRP=t9w|=Q8#{1O97MIp#1a~8KRPsf>$t61ekPQQZ)7~aaKReZKy#bC&$F(ADYUC zJImqVi_MZZ5LHM;5JN+LMhq7oN^+B}0;qC>iLn>NBzft8>UW=ZSW+mQhqgq;^be%sC0ia1fTZog<%v`<-Gq(ZDFBA%UJY*DhX ze;6Dw_=(kqs3>FLssij}bn}B@$O`MT;NXeippt-O+B72ocb%9aE_R!B0wA;*oRQYN z$lG7cNun>|T?io`$cY)BUPWL2p=!U#+m3avJ~QA&s+AP}rHO`Eb8ermx5o58C$zJ( zQBE@}1n~T(#Ak!ZFdr_YtWqx40A-}H1mH9^QLN3oguw);D7F@P0f8WXEL7jPWp7~8 zC~3_!5v;W4*VN*%$r2ivK|v8A{Dwm(oPe$9v#2_!V*|rmK7UrXtzt4Gz6c1zwvJje zTb9rDoK_XwQ#&X=LOFtH!t`L36`{g)D9Ah!JIZerLIZkc@NG$964X0#nV=f_xi0F5CO0GlE!HjSi9!>A|i3O7P~kj`nBG(7}vM-R?r z44IOE5@BXy0!eMjSj8rha2&-8AvQc1NF`o z+1$)b$=AGreEfb7n$$(@aoA^$e)NuUjBt2jvSnfEqurnobA!L@WXA*6z4^CoJ3%YF zW*rW9(<#%KZ7;Xr+&+8`^gF#cSt%{;+oGCyNXOX65A+xgWz)q%TRxW%v<{qD3=n~6Sp}!B66p~U9Ke`8@R_w_t9>3Ak&ibDVwbm7pCB0}Pbpfqq}6NdaUa+#mnLz& z+qmS?Tv=N&`}YG+nb>XTCrK`3*UNQHB)(xp&)`~&)RcVOjGHsyvkIs1#H3mqUiXYy z1c8P}IVct)LBegr%bY7j6NesDj?MqQlfp;4rD)h8Rm8*icpT+v8n^F*Tv$}%Ro#mZ z5hFRv)WMxmW*$)+3GA6jSF{r8e7zkdi9-y*iiBsmStfe&M=B%g*WZzTLl5>98$>^d z3IS~Km!`tE2{e_pk+n0H$R4d1{OHGkJstSon7g62g&mRIFy^dPDgHX1>w9iPym~+x zy(_x(Ef}l8sm$-4TgVjZhf9++!-Vg8=@6Uec@C)4iA)Con zH^$8<^uuAp5iymo6$U3RfnDUl?+DDg1*f$ZYPOZfk!g&g_%B6MbAT42y*Zn?gV|}AwfcYQX8hnAJ5kaCXy!me4OPo*eFF|P) z-@~(W37TF(ghfSDgp}>CgYU%98BQUm1}}U1VliF&p&VaY-Eo6xVMV?1v;9JV{sRsA zXF($dkvFeBLTx_2U8H_6nlFJD&TWBdt6kl1Lk!LCqIWDzH4dq%-AGk^a_xbUI!BBu zdWV;4;Ayi*uprMLI>R9F+9~-y)m>utN5=yVRft2HL|9jpBSvP_@Z-;Q-aUN8g<~nfX;7?tezrsi zg}r$}_YJhUqij|ugIs-{XJy>{DJy5xQWWp+ywB6$i zo}ZRbA=YAy(dHem9)!x$?L`Znf4oqVko0y}&qwZ=atL>^l9c~n0GJ|Y-8pgn3`otP z1_!zTkbVNJ0jbe%tHJ*;GG;ACO@<3D9wZTdw%B*b3K^b5giyWucx&#v*f+`&K@!*8EjE{h- zSkY3v)C+~a#8&Vt25B?TOKhdL63T>Tv>_-H9o^B=q^F$MJ6eJ=cyG&UTdEs>C*j$%q3{VZAtHy5k_o&IUn)z&$4x8oDE4r9a!)iun+7>K)K+u&!pSec zJ_&5C{B-au5gDoU&e>xoPqDk;{=l0F%7FM1j6(Qsn6japS@OgatTX^Oq$%|?0dYvv zS4>a>otX>95}K*14PL){-n%D016y&B_8fUv>9R3{QKsBl==Uw3_8JFe=FaP-#3+a| z*$`YBP|*RThFdwKx*M>90Lp;B_&v-%{KvEt5EdHJM2n3;8F*o+yO+OfG_fT6h`>s4 zJiT5X%3wiYG=Hz(!%!wDwsAplJUVEUv#}o%jsO5@K$&G52XZzUgn*iaGI?6ltTkCi zR#^*TF{DDnPzKH&=HKClc3YBuB7WmHI!Mp@iF%HqDN*xRA);=&B$>Ht=w4PS$GDwk2RXRz0Njt*sSr@32;NA06a0ef>O$rhNye_m*=XZpsw(`!1>s z;fPF0{s5~yGDs(B5}6S(ZP*xW5`OiY)Lsl_V0GaZi0gZ4Jyil5fig%TvGX8{q;}BQ zQLx6qzyeWaj3$&p7AYV~+eE`43q2f$8fJ=#Oy&5CNRMpV^2*|kT}s#A-}7fzSsXsC z9GTPz4yqp43<@dxeWUUiCa^!05u~e(Lm5F64a_o$)4kaO>$wa+u&H0D50_n1Lm4~@ zg~9X?O9`gQGoGZk7nC6>JDs=;ZwfbMbz(0qJCfF@ujH3`!5P~=Ha{kZ+qwd9YvX^aSElYoGrfDR}k2y&TSokbbPbN1|> z`Fa0``QRJADc-m9QmL+Vs=MEFp2O*`uCA`CPJ4Vm=XsuQqh2ZcO)xHl8lP6*Xl9p0 zBeHH(GqYp6a;m#{iA+}E?NjGA-r<5tBNdlt$BJu!ZUw z`+Ozsl!Cc$u3SzJ?HtAxzQy09Qo*FGu%OK8|332hslAh?%_y2$I=f>2qU!3J+M2Z= ztkJZ2>-rtLw|siE9!zuY(x0Na-g9d12q(2Lc8tcUwTh4~{CqiBglPpJf`0jXv`CEv zpiW;rqXJOMR^woe(%!RQ!E3gDb3`E1V9qc+m}%hPc6N)`-7MU!k$rvI=TW9FW;4&V@=44PWeNic^E$}Dcg2XGl?EsrKB+++%f8J z62D9+I0icm6z1U3dR9kp7O)HO#isx?hmY?8=tOg|=M$V?jh(5&*i;|VT-X)=d4 zpoHv#TV*!WBV}C`tkJ~woOyS!&(OZznQ1NCeNI#A-P?dNe+UX?dR>Kt=$Db;Qn<<( z@Ed>#^RRhrhccLAjP32I@})iomkq}W6UyWj3+#xYZ0V6%&|cWV>*KEnQoK4fHfO;R zQ&RgIFOjKO9RkkCDsJl?gZ4h=L zjwq+%k*0=Z%K^2L7cUr+?KH@!KS(-dy#2mh*W}WEF^_MFO-(H+!x5wK%KA-fRX~Y< zd8{@zi_Va8ispLHDOnnV?VZ5{ucCRooB~UxxvAAVtf5(}JNkOzAk@LYL?UIh`Tm#l z5EHlwSG5212}lZZutvKHunCX~f>TIV1;WD9=H3B%MxwcVy=KZO-5fN0xemu9yWZYf zr+RAE96qq+=n)wL#RMqEKe;Ypr*Ji7*zE#h%+X81f=kr;f@yLmb`w(Y>gXuoG5{BC&=TP$tTA5f!ft_~jD6xY&Ax0T#}qo0p(aK{DyE*#j16UI z1qP-_J#d&%#>_5h1E;0sSJ=UTU%7_3OJa&yOY>K^jX$^~QuDBtaLq_XFa+_DY(o+nZAWxa?9FjT*m%(5==s5G$>PC z{)7f56qk8w*n>ht;twB5g311NNR2tFDJC0I8=)}FT2gKyMM*f-ywNwA0{=n zVDdY&E6j8fEC#1cFfJqECWmHHOo?v@woe5jY=xu81)Wk!Oo^6t`?2rOgvc*IHcB8H z@!BK++?@USd{bZoTmm#Xa^YJ53WB6?y$mW&d05lZqn8OZfo9C%+-$K(T1yg43OF&& zHr*^U5SeIF1}BROHrIe)7VO>5*SG$Q&zd#(iIGpaQ~C{m>)~jw_MM`yJ_9@FXZJF3 z%+!g!u*n1nF7=+>O}Sktk?H+%BP_r&!$zV>YPTnc#Q+@QxOsZ80U-X^HKr+s9+`Nc zOz(S}fHF_(rW@(~4Oj3KOK?db3)v<7GJ0dq>%SKIX36%Dpv?UDy2%RP4rPceiA~0T zmIupB{QbTN>=JVfHbh2F(zMA8&ugQI41Sr>^BYcs;1$S*f6Y&U(KKa~k^KY9ER-2u z(MdvKqPLcI^fQ7=skEvQ%9K_S>+Q=_4{&Z3d+dwIYLt~XhKnOW!0j(_iIh6vUTT1wM;s8<`dZ%0L{=i zV=lc;|8$^w&tU$I=E~41`VGNm&`FIGaweK&*ZIGF_1#}joW68Y>Bxl^99bPRBxT|o zvEDS7<6Qc`{|rnn;3i_RVH}cJY+3@=VBz6YU?qGa1DKMU$b^-tY0=r((4ur6kIjWg z6FN9)0>M=5`4A%v2xj}2N5h)Wy>8Fug{$YN$=riR-5pH?;1o5D81Y*(wIp+|Eh?CN zn*^7XH3l?8G)ZaU;t+xpiDJrezMv7)kD1#$LY%;Iq zwdtRA`Gf_^SVXwV(k2sbp-j(=TN-Zc%*(V(1g7!n7|NY`oi`)4u&&Mc@-BJBzt1Tc zquWxddnopy##d{SdY3q%j1Ut-Nj$KRa1ya+;gr{#>i4C+SV0--qfu{{%#mv5xyG@%9Lz)2stj3>{1QEks6(Ymj_3WOF5;605o6% z*aSs0(Oh9lcuFM2H=kd)j5)$49e9XZ++w2`kl1SY#IWXVNAnvrxlH_Xoq#W@+*b`? zax<7jQJ$IQgd~fU0J+4IlTVvWVa|hIB2zN$-in3);>R6>^F^QxFvUbRCR~_4>rVA5 zrN8E@FAnYquYvFUag=V8fx>u$ON?Ri31!$G0Lo-M(uMaXg=FGV2vBBK-6Jtuekabn zyw1me5d+GUv=y6-I7bpDYJFpl!OkjeCz!?@hMk$M+GPAf88S+ixl17z$_%?%ZNwUL z6_iObV!_ zDUAQbQ(@^4mw0kQZ;b32tk#!kl1#ZaHM{b6D!8Oc%r&EPyPB^OXoAQ*SQ4X5H}jEh zf>ERZSc5V=xF(cg7?b9mX)3!EO$1|62vBBT<$ViRbvIjniz^$J_gNFKK$*!6Ehq4T ztV!5pH0MZy#A(PFY%|MaLK*sc1Jv|#U6$-p=!G(}J-58EuZQj)KaL>*F&dQ7G`8wC zKPQ&EyyLic9vC$;O&9<3&v!2(SBMH;E8F$7UgRRa`o5VDxvi-of zV$8k9ETh6rJb=+KJqmUsb;XuKnNTiaa%1lFwgP4Rz^R!_d_kGKB{y6X z$K)?+=h?sE(@8vGe!&6*WI=?CEN#J-NlbR8Gk)Q{ipZEY8EN_Dy56b1!GJ!&hh#%% zK0meB&Sg|_j6Ag{<=cPp!oXboESW$y=l+)ZbU@2DMGernh*QKRzb1<@1mgzl|7b2j znnk2&MnwqkGtt$G3*VKHrPw|>4xM^JIDYZ4|)$Rgi#3SQV zYOK=mtRCw(_uu&8kOkFGqKbvj=M>&HBByIWn$W*JUAD|2GAn+?=OPilfSr~PaK2BRR?Enf^wiX{-I2HZ8w23G|QO#tLvIs*Uo?O&8&HI*KDps`4gu$ zwGS^TE<-g81;83-IedH%FCkt^b1nm9`TAn>)>KqxMDto&DBnaKuh?f?2aVG;9cRr= z?kERMGLnc8i40C695)oWqz0>UN+l8aiY9Szij&t!E3hgCmTut0chjf}VP~jG8I64X zpJmXJg};DZR;>ENly~kxl}mchn)lrNMFWOq^;GMB^RTd$5cz^s{E}e8`C@@GQZ%Ez zByZyty3#6RVM-IxQq2M(Vi4vDhVyF$dCuOLiMc55{N>Yt`^F9S- za`NuL;bLmon_b_`|JRp?_b}g^afQ4*m)Y^E?J~AGBoRu|(n%(H5f_YQiZ;8M^O#PcgEPeuT846<5*lZcAWR?Nj`(>!AiRMx;@B&hZOkkb->01CzI%Jy6dSLQR zk)ksso1%HU(nMfN3y}$oEddCy|oT%qXf0l*p1+u z&`FK+)Fg!%hOHFfy|Vs?&d5dpP3U|9up*O{EKo+vE(w2;flcN{)UVgAR}q=VJ4F++ zBoxXZv*mZy*2l~r{G#=brGb<(m+ zp%==$JT_BZ(VBxxyt_eUkY)IkQg%^;iAlK3iVb)2bXxTtG0ZYmZO1O_Kv@i~8QEyJ z$pF9Pp36xF7CZ$&gM`Rfpp4)fl1h`7byHAg{_3`>d)TJJ9Nu#LzdqL1LfiIjl6@>( z2BgA3CCYu}jcTJ$D8qZHp^4^dFqlqXIvL{X5x$e2fJW+p9hXU4fn%gcCIOP!(kkrJ zik=!WN6~Ndb#ii0#wjA!8|K8u(T$Wk(S?$`-{xq_Q<09g;UUso8UGX5kV?*^b%-BJsmya zs&o%wA5N7MHXDE8N=^A+c#Dx;3cpY$f6j#1ykl{PTo_Xm6PH8_R_Cg>3zxy!#N&$9 z84#8L8oT_e@lsRPKCJx#V4Ml(JGDa@SPc25>K<2i=pa6z-26NXi&4|KSAH@o=AvlB z&UJpcOiV#r>^P==-_{hvF-j*cUbUcdfF?ESfq_KPygw->x?|4%{532_en+h<_z1A( z(D_qht4Tm62(}WK%aB@1u7T4|X)6b1oD!f<|M&m*pSuuNqp*WDi5G=08Nc@F279~d zh>-*n#|#}V;C}&~Fnx)~7AVv2$(t-thJVeCN`3FYHT8&0a455COQ{KE)arM~ks9p> zT)S(Pms7M#c1c9Vj5dmh5R>%Fhy>PPhw)lt{1BzgaqYD1Qs{*;^VTd?ms7dp4Q)>; z`qk_|rcK7wvaPuRh|Kr}ZOjwaj*MSv;_ACC>}>s^S^yL5hCr6Kr$v=@-37`_T6vT9 zYM}?pOqepXDHfwNdF@@Ne>%|A8fnRU)xo%oJ;4OBiRPLxekEqDD1hdCIsuwisRsrI z;o0G%qj^t~TIYU6Fqq4rFluakvIuCRx`cr>aFCYB4RKgt5FZq${YS*9BC!G~IdhGZ zgECGjEP*mD-7CzXZizvmmxgwaCgVshd0|T6gP6rJJPkd#B*2CEl03D*0_uZ0uL?$?t;pfNNK4#1B)P5a#IEn+%a)xm!J}sLK(E-z#erucv390mXsAp)4N+{^2V6jc(5h_ zl%ZwD{B)9Ha2EdM7IDwHX@1I>MZ8xo&7vtaHw%<0S=H6tfnnHl>e@}IX|J9VfFcH^ zSd6AUCl^{)P2`e_XU+=1WvH03n`L;FK491lUB9VRE0g&3A)W%(vLN+JoAX~l8f!fSG;^mENMK4r#>LOG6%vX(933JZ+PRI)%B2`HIRMocuZp-iu? z8PBFdvu|inhNf8WQh$7D1C&|6HoDIDw*OtnDk%!Nd?2cMmZu?+(K&8VY4Z^64?BEaCkjd!9`E$DisI zXCcP~pjo`}H9t^>x|sl9jR|q&G&F){*SxyhG$<25<7>Nb{9^LK?^ZUoj{4%y>&LIS z8*>e9!S<41Q_vQl%`*EBe-zDSWB_?rn-0(BK28_~_z&GsR6^+77RuVZQObeC5 zVCMQ33iIP>yj{^GDJ?C9X+RrP# zZsw9Ul{IZtJBeuKyJHg!W$f#GsET(UHd0Tuanadxba3WnLKloOUguNy{JL0?E05r#c*vW#5 z2XxJ_UA#6q6_1d9vdq#p1#R&IWm2hJhE-@3mVyFkh=(u`Dw_AoDU~EyB#ba8E~cOn zMil?NYP=_{XgT4dh2U@|kQ5W*1Rl%HaiWO;oE(&KN;egJSQlj|hJiS(iGLm4jr(>9e-C9rvR$_xPmF?9Di)%(iCk`UzBoIf{ z-=u?6X4lup_J7+v?L8UMYjIG^UYsVoo-rmkKM}NaL@)J4Q&E zjE>THO@OgYW8du*7|L+x<$DRD>K-}81J-=F!q58ci@w$4dsqANz%n%2qP&W1FO+$(Zdiyy=Cro%#UNcg;hy>$*%nOIwQ*t_B@(=M@=yxRW8GXChBHHJdNhs zvUNqhZc?vf9iT~J@wJNKMMh93*A(j36(GhrUs6)Lz6Qrc^k*AW-(9*4Yrgs(QWMcz z>Y$91gEC32ZHIR+dVkuKx&3^ujbF`%;56z#Mk<;k5}G4SCy2cz4clbWN5P?tB$!MD zAd|3L5z1UMX9SLsb2ch|g9n$SM8=%@6b#Db&zazbGS3X@=##}TXo+tJH&gwwLvl3# zT*hLSu})&Tp($I6c2A6yR_rO}@=}Q6Yqh(ROv_9(|F1NHMuSvZ-_nVLSAxdkb7?ce zDXDetiqD0wOsp=Bxr8_0#e0DaXH`75GCkJIxL%WV0 zUcPY-bY@gRch1S}J}85cmu(iR?AN{yjM8<*}JwDD%>&8|*BmctOUJ4~p#dG5(unsQcAs zFioqz!(K7tO@4tthy~eiqm=%d#Dq7pC0vOa$NshA-*Hf;xl7^{9{1}2O%y6!5I!ug z$t9hKOgkdu)G|3H>?e`<)z^Gk)^E*S*Ye9m^fo#u)IGh_P$CVZZQfr)ZvGYeAWR0O*Z3w}A75!F0T};3ULw}h|9_#QVW8*~0IY6L zJgPlw)^I06j~BG!2c)}G+KW+NFfuu}*iW-s$nm>@b6bT;gYJnGqN zF*)ck7k^{{9A@h&i`5sGv#c77Tvnh}K`C_P5jZcrKx= z5meFB(K<2k(gaJ#g!13tl#sZf)?DSqCX>dI9ti_cI}h@x;1}|;SnurEKa}OB#|OE* zCo{4onpx=EiNaW1;lhH(q%|&h^R9so{ctZ`nlD z(TiH^=cm#e*N#k;@R0{?Lkj~nUo#Y}c+5g@tv$7BYhL&}VqG2AHk3M=et^t28vL`&ua?mGNY=wL0B0x#o+QBo`VgIKWYVeLPxDk5-5g&&9*TBfw1O4XUlri>GlQNVA1A5|6@-YMHopF-Eu5PeF38b&-lX`nSp92Nf@*e8H2Xj>1$#etEqw_7bb!A^ewZoGI~DK~zrKGLPhM=3On zgTY)mr<9ma!4rcLsR`p`uT38&{_ezFZHCd)GjqGSvmT^7SA8$hVAK`NB*tudT~+_e zS0>=;-}PKu@F~4(bq6fiGom9iHI>h{RCTgDM9_KP>QXAFU2;Jp>uOfLX*h~qDq9jY zR_YGc&&qZ?M;9P`(Cm9&OyFLup2-`b4bAyVAF6=GtdpiOV>j?*gS9U=W6WUBl_uv{ z^g+JJ0L1F!Fttug9y~-(+wJEpE*TziGwjs@uz1pxQ3$%Fk>IJO?B{$P?!9Eu&{6T|R)di2fI$T4${M(z8FTTo{*QH`#QBhBU^i7({Z+e#YcTqA+j`tR4qZB=592WDF-t zyb$_nk!@r(o}GhIo9B8ZLQ2#@Sy-*6RuN!$1BONE9y1Y(=(YJ-DBk=oB^3w0wMBe$S$HLBzcKlV2j2GS3uDB?CRD%uQWj;h4?NiQXufysgsUBH&NmILJ#Z8tRF%;OB|WL zEanD)f5%14=g8w&BL;tin`@jugW$r9w;<^3AabA>eXsYW4iATTy|iu{6TblN&d;_` z_kw~zoA>TQK|4VW=$;X`U*MQoxDgKa*)6+;;zPv%lRY~~qclB_DB`Ok z$bS}&g&{#Mclwdvw*U*ROpJ{8&7uWA7c}78+awC6@zcpZ@w?Z(o%nmDq&3KWz2PSg zFP^6fPO*p?Rjnkdiqad8D5jk*XZn%})um`}Gyu)YRB0~52NRWg4DGdCT{F}cNU0SY zzG#2*AQShPNUJ3>MZI7$x{H#n8-3QgsckCv*Xv_l+p@4q$1* zfz&DDbX(;$yeX?{?=J@mV(R0jhcsUi?y}>~An-l9m>o78=8|uhgNdU%Y%V~TsstR@<#Rb=eOi+%gmmsEw+NMnhCNBKRLXBNsS?!%@N(@_j-Li~??bI% zb%R%%#4mTo1b-1aR=ogOWg0An1A-RYrU?e=JO>;BvBpGn&Is`ntiuFd9sqerIhH@X++JBxM%*Q6zoZF zWXhb~yLv-2HXnrqJh9Spc zX+O*Cj^JjA&EL#xrSF8hyeL_1KG%@#K&|}tC$yrT%7im2Z(2PIf=Hp6HRT{oRWa1sa*%y7qpn-oyq zkdu+ESbxf2RF1YjWZX*t^oGTYi@M3$RRCo_K?i0S-K2q{eggWMf#|B`-vgTnYEO{P z{gFN_YSs@61}i${s0aa%9dUD#a|0AR5;k|FIIuZynMcy70Da)Mw9%v^3&%_am`KW0 zAzRu+G&b9@E(Z-j{N?(}WT^Jy5aS_52W?PW`aB{ zY_;vWe{DNIztZygW=;qPX7);Q6!`Ak#8Mk@&o5Nr(!Z$fl`cRC4i2jh{0kE&v` z>s_~hJ*ppnPv7zOugwQf-QQ)g$*>vCx3$g7#NojI!eN${zgvuBfxQgI-rio6rI$;W zHh|sA8;~o|V|VfOudg=uA$julzw=Qeft#CKsjc6NA5{RGx1MG!xjIG*rVFvO$6_co z_3eRp!M&ls|00t^)<68d5)hFLs6y`v+8w_3xx2mk7@vpj z0rpU=z-DZ#VrOi)vt6+7)6#0eZsksxYU=3|2BsymFtpiomQnCGunU$TrEO9k=VFOfC%v`^u{rD`wfmUBMKPjrCP#z{tvvh5aD`8_`&m77&e zDGPFgemr7X1pobWziC-l-6bsssOzA(bzdP?Dfo!K0X>+7{@ErswuA%)cWD5DWSD?9 z1L|5-YDw+03ptQ%2>^*_w(`TrLB+~XB&q=Zxz!|Afe4j2FtJJWV~BrO8}U4T{`8e) za<0ev`}>rnRWR^Ngrn_sMBlo9`?`D6bOkzHH2lGTaRf=oFFh4(`RbiSm!ueIbzl=Q zYJ_b%kpuxKt>|9eJT53}0K1z7R%nIa76Vy9w;X&xZDpBW;e z06*Lb_IDDi0q@r@NCme1tk1G4_N-jX-;I8-?|3;trh2Cgd>YrZo(cNW6NsZFG;^)$ z0I%Tzb`C+4+<@Hx+FCN~F_EjV$i@OM%5qPPrA&mwo0EM&$(z26+Jlc~F2Mu+_dwuT zJRc71^sMSU_m$e@psKtEDT}6@&v?FFQLFz2^K|CgSZ zk5S`6;7oMxkMlCRiTZDR0+Ls!UX3~Bu*^;+ghtnMe=pay`i7u zY8b8@rp3mrr(prAPSd?|3#Ql&QT3;Rr&6jD8F{Qvnb{Mu-`P6KhNe$%gH~^!*~J>& zFyA?EnQ4u+MG@-4y{m!SEs3n>gIl=#iZ>#L1!(QGOKgPoXxL zaaKg3PO#<>|k)oVn5r9{p6bassCGo#TTg|;e`rK5z%MNk3k+8 zFzj1PL_}{2obS*t-wVVo*-UQLQMckG7~B7cR-E~p!?wvK4*I}ml2cVEb1XdjrMI~V zd&&dTzL_5V2(THz31EWF=%WeYE9MwrC`PBmd6hE!R1$2){1W|QYibQ$2a}GiRk1*e z(MDo=AhL0)U~?@=J4mD-o;l|6hv*j393+e+ED%yd8!^9$9DRyoWC(e6=HMZ;aKsh- zDthuj-)uMrp113)=tA|%q|b#9%Y^UOugKob*kVQ-0YgGc#fzehhV(+d)Jb{XH`hjd zD9ppkAgUp`l(jCI6#i0coXH7%&_@!he02n_7SNncF%!HAp~$<_hk~rWH{$vcdkHn$ zXe;kEq%!eLW|L0Qic9+b*i{1>om!)#2!#38kk?(Rrlc5fQU;x}wKAM$5%Jgd^PP>Y zt&QSCc7C!u3PqW`C8eInq|7Ki6|?c)OWX8+-hNWBtLWI^*qcjtGu0(X1quaE#4 z3ES5|_{S>lmW(mK#Vp|KCI!giS8b&JX8Zr^2m>35f}A2kpjc>N$>tj%erDoPi3NI4 zzm}a($fK7c-^-kD&M9biSpDedJN&kwK1a_XjnYQ9(QGhG#p8uSA zqMTpfL}>d=U@^~!Qp~;}$^)sPxuJ*c740p>B-4y)HzIHvxg8X4t;l(;GxXN%$oSTT zMu#u6;SXst2!a$MpJ*R6p{LqCc>8WEU`%<>5WoPlIuig8DJ>zd0w5dQPa;oeU#m=}N5+xtUm zpUF33jJ_EXrx%i8ks3*YJr{Osb7e4A9kfD?iktnONvu{bLm=Sh@xr(*MZMqX|5kQE z$xME?2^*aL$8;dcF}<>0ay%)R06M!1!_TNu!jh0x;oy!z2~W#(;a61?H#BLIE=!@L zzEYX9KYbUn(5cG0QwDRC$RG28(QxD)PV|;10EYu-wB0u5=k4@dx^*1}vIg+O>!Wt# zMe3#V7p2Xz8*5NYw&BN;FED_K*D0FfWfr|U2#Nt<5@00x9()K-KA&1e0X-jJ0?Coy zJsm|7hJ%b8vz>vTpJNgN)Z4D(`)LYusJYD47Ui%VKejcFulQX!H5hzEmB4SRbe;Z` z2$VXM|8e~_K0f~I*HiY!tB7p~6_nHo!BR+p6T}HF&0bYi47v&3P7cL|Owwk@$dR^5 zkD)M*oSd(}fiYYCdmY>kg$dy5!%i9;c|jO26!?Ycnj-%MB6z{x|xDpkXH%=iE3v zhy2uy$Z|J&2&MWqaSs}miStlr;u~P5D~QYR>g3uNO;&-lI>k-7O~3bhn*~jxQs$Ym zu2qI=gNf^a%}c7!$MV4|(zXb?RX$oHe!HH{a?eMm4M5B<^mdz*0Cqjm@b zh91-UD#rP)_wQO0`(VXM-S!J!fHNEzbjB`^Svs@xX zsb(jsOiFOKdN|lBE~1y>%aoHEt<8`*CiT>EdFuD&_f^X~Z698|5ELDcdT4N&xa_12 zYl!V4Drcj(2zK#{3UD#STa3RjWb;UUz51tOYyX83yp?(U_8sgt{_=%++UEDK^kHHc zWLGp1zGmSqpxSxVzk4gMCaQstFs3SL9Z-rH*a!kHoOp!5f310dkVo%j9hT`l%eb!E z{qlG4bef7Q9tQWHVl_`rFVOt2cNzOew`BebS9=W>^+<_2xKGeIAS}?nr*T~W`OrXV zsO_RwC-L6l@I-h({{UUEa;Bvw$WvaR>Zf4-%HP;E<*0KD1|kJ6-b`RST^6G9@k|JU z8vX9m;b%Z>*%Pzy68zRPFC`MgeDgQc`PF(Jo`tq|7kCm1Fe9{q`|=6YR{u=F@Rjp5 z`e4cXaRB_~MiI>XMgw?#*J*Pkk6ZDI%S2_ba-y#7-I&W8TrxwYn`z(guQ=@m{K#l;4HF1d<9{==8NXca({SU?^gV#zY30Z}Nb4K@Vk5~;Xyhiqwz0WUug^kj za~W}%(�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 diff --git a/mobile-app-new/assets/images/placeholder-profile-img.png b/mobile-app-new/assets/images/placeholder-profile-img.png index c45384cabbef1b70e9a8cbb081d8ea8d1f257ee4..d9c61f51d348ba7c6c4da0493e16f33e8439daea 100644 GIT binary patch literal 22460 zcmce;gnNe>_(NJw`piqhTP64K4x zyzlq@-RIsv;P*U-N1QWf@3q!h`B`i8PF+<28;cwZ0)b#FDat;BK%j@WKZrZvlV78A z;ou*r%QJ2wCDuOf!rf@Xdz=741s~>RBi!{ycd~13I%XS68f^4z zwgH8;Ag#2?MRUtT3*ig6>2o&$1OUSkIf4||yAR(0XaQP6d|F#K>68LXJ0z9NlQtMmw z|F)R#AGq`HW>QixnBzLZeSEh6Z2_Sanf#Y32trW`f@27ye~kX`W?&HoivM=TM6{xw zn8+6Vw}s=nCEEXXriDUxnN^VN%VI9PcIPL7=`VU{Q! zz(hhZ?u_3PlbdU_O8c4b^?i9N2A|8ybUmg~Y~P}q?WUfJY!^VgMv8t0L(P^uo|fm`&Xs$hnJszdtn; zdrM7EZ#%dr+!e@=6CWw#fhksq9%zCfBHqs0ZOMF6VZoK%lUjrR;ae=<_EZgODT?AL zbGqxpD*N;WMJg5|xKt}OAlBzHh5{Z!IPbA=my|PSV<;fk*k6ya(YFB|=YD^Ls-zzg zHq~VGg9m#%Gn{J67*GwMOLDQY!n7tIq~zond(woX^PZk1R@<_c>!ln_OjX&aSfso0 zL{j^`7Z}Isa7ts2BA04S16n3b(;0+Zg4ZfsFGb&755~>g%o(OCRd+=H_>sfv^3;jX zdYJog9KAx$i53qFDs-rBNymXeFqOUhrtf`qgv%-!5HTOuVN8jemMZkuW4OlQWbb(t zxikchz@dtSz+cd+KYJ$sfjQuD#cK{nl)FWd*!3yO^=xHLQ(r&4f*9qB2MN3bsHd23 z#l>^XXzi2a8uVVFQP-QGUSoIp9GaUeTYtX!m(O__FNr~_iZ<(3JV&-Tk%kY-Ja#3{ z*CcPA{OQ-m6dT#D-(pgK{J4>gO-2pRF%s}*v##+~8NR=dwf@m!w*R?{rjo;Gl}f6P z>smS?tElJ`{t*-xA|wGq>8@* z{9cw2LP!OIGh@}xVyp+_=jT7nym^Z1lf2Q_yAw$)xt6AB)gDs8-BkSO{^cDg6kbLi z0(r{ZJVR!MM)_s4GVT}AOxXH4+*o6q;L*I z(m`i2w|4^qR1B)!KADmy(7*08dg^oh=TpOOb7)ykYBvY8K7tAPCRvV9&P$ol#Nv8A zhpXeG;1HH)r1xrPBOb8KPGH`o9r&M+5Go0hF%I^Y zPLhoM71q`DAG-pT($Fi(8s z>~c{2T0sI&Jq7GCxEi*vgSjWqt|opYLM5haT7_7?3+R=&I^3ERJMx3RbixAG^B#Pa zjH)vb35D(`?T1?+7BRWU0xp*%7w^iyqooxF+Vd0p-ccuE@*^{Wo{tr>zHKaDqaX+7 zy|+HSjSwk=n?d|62x2$a9Kob^4QFVVLJE_T?%tD8DS*M3Z~b`~6-q7{(YW8;Q0Xvt z%{`Lm&j0(P7d>#ga0S4Okg6g&Z1TmyS*7a+TMU9e(EOJU;VyGDkZHddu%J~kI&{Q( zPRX42mS&7&VC$4aK)A*(r=vo`HBf>b$yDi=qzK==HZ3+*Ru|N3t)J5$B|-c^-jePj z@fICASzq-wPwfVu;RQqM= zUBK5#b0jS)0zO(irRcKuvbQdRsi4SvwvqCs5A7}KOpq|@zL!o;u!vTXsbm>Xy#rY@6&%Q~F^VMTcX2m1^Y&A*%$X@Xd&c z+ivn{VafL&?p%{^w|VR)agB(uc><9|fHlK!(cMjoS=reHqJCZj+()m@cN&B{-5&!* z7J3Z9xkB8f3`DA$EVO^p(y7Ma6MKr>t}tnPhWi9DAVqr#7U<}s$1+J?Z%36L@lKhRo;n9F=TK3C^VDt}`=mGPTBvmYxV2teFrm8QLS`!Iw|`!oy^ zomEm&@;AB_>xsUmW=`>9QVut?00)MR*@_Ze4m1uYJUv^J3n$!5IOA`({juvy-tDjhlUA2g7y4K_F zpLz>4fpn}0Aw^)M(zPs;#&8m@>k(s^jD9+#bTJGBb-YCgkv?a1Du+;+Je-b#-UWiNjB3CAleXoyKj*!$Z0Ak}tLs=!?xRba(X!EJ%w)kNZT^bL}axrf|o(;08!6G;1~k#MY~NEEKSn^D{k&TSTO_J+qdH^v#ZJPj~7&5p~U6u zZgZ{e={gD`2HG+gi+6B+DRjItRfxFj)}I(n7_8CUT2Ag0JVokFcek?br|{SRho%A1 zq$2Fr4Ce{L$q;#9(tJjW9AwbhaWrXaSXsVUbqEvc)={LBIV;LSaHJlXSr?jA3Dy0< zw0jIpn3|j`Hxc+D}|vc(x*C_bbJ93R)6(0ZnySt~TIxp60}cHeP5To5ucqNc2@P8Fzi&dkWD z{0TQ68mJCTJnZpZt51MM&LkyMwf`@_Qr6btugE_atNu!~vPMH5D+38VpS)C;phv@z z%F}`PbiYzrCt$lxA>Pq7X~E+@5VA5c>9eJ4X*1E$MJ+BZX=&4V2SeIWIruDUS#DeR zR>*!pvOr=+HjKofH37n^H+TKPau+HD6Mi_+>_;I?6i7mA5(_?;bNs=C8|c2R|10Nx zdrE5*76nb=ZR8oii)VHm*04*kphZKfrceILy5kCg{sMP6a%AN75UtoyxKk9{!2a6hmCrY6cv&_dTA^D zgEI&af(U2nIDyx@Z!h~$Y3XDc2#%|nU&x%u+|oiIB}Ly~WkVu#nk;p)lkWFWTowcR z2;BMRD9b9r+@GUBnrV|&ecI%0UW5&q3`PRYqw0ac`Cykg0wN+<4}BGBc`F#rU*%q? zJHNdLdkGv*#LBxtUu9&vSB-tTA6wJp%affM6+1{jH5>8;HP3>W<1g3G^0=IqrM12eE3FS|A(CEO2`(gkt~BNk*kKns-ctJiq;EQm%{TSwmC z1BZgu#-GI=6h1Ijr1_P0I!Rs+ZMzJ-i&4&<%dKoLq zF)km$qQu=}vL|Ki@VEh0_P_5u!XlhMYBQ*Qs-2AV&|b@D#>D(PONogP{H=CwYQwu)xAYNK^1xVAO2vLSm>yo=#GW4WQUB*%JugnG;zASArNROh?*yb=Cnx`h`pTI zKYyY!Vin}1dxgy61@3b`lN&+tBH$oT7Pr(=EZ~724$f3al-~2`W~HAYgzyI=)qoqh z=v2oW0@bJj&Zp&NS&ujBmF`CK%)-J-8rvuV1RUh1;+{H+pShuj1T(zXwN7T!MU>#t z=-Wqs=;DnS!o<2oL`1p`>4=KbPsDb|@ij+?ctwY+ZUg26p;wyG5H(ZLv*o2>&1tzv zuG;yhq&5m8wDVlR5nq>+=DK}sov_FZ`lQhDDp)%BTz#xW&$JMG1c|%>x^giW+E4o) z&cn-V`8(OHLpP^h+tBx~!-zI@N-z=~tU!`Q7cmLvY}|X=_{5_YlhWr8t=*2K_Zp6w z{NFfG^CkVxQt$GDk&$hPHlZHzAyG>!NtV*5Hv-q6^ipt7af0qL2tJUN0z`{X( z|GcwbT~jQOw~tcAZt8V7m+k^VS2Rr+@flIIq*e_`EM}-Bmvxe*Ky_^Be|CmNh*u=C z>lXDD^X12EzA8XNk-38gw%tUYRn5EV&30*BLiL?yfm%`DLBk;&2fgvN^@< zIHKK>Q(LQhfzJ8@w)=VvII1_8Qf~jJhR70vW_%BoQ~x3u^!`2HUPTnHuyj@wuy-mf zN+i+^Hr?@Of_KUrinZ4G_q(LJuWwpAJWB-emJY;`P8RnN-$o<@Sx3RQJc{*Cf;<{y!NlK7SUgyaUAm%!opW zIAa-G(NmJAq-3s@_aKEx9b5!)Imc%3m>WkdCn zvntNmV}^0~tC>$#d}uZ8KOQg)n&Wwd*dyQdnDgWR)|que_T${FD=Lpr^*%WZ)*7g= zY{46-f#T{(x4wn6&@?ZE&QZv*`!qYu?YM4yT4ODeB*K6)17#9bA~k1MnLk|$vz|gm zX{Jc~t%$nYDx6HaVE>J)yG`8}Z{4^>H~|n@D>`YKlG%4k%@c!>VBiYsZ>}j=9zBxQ zhWAszDS;nl2?1N?H8y&Md)EUqJIa9$o{sUy$%%DIp+=uwb4|P%%lq zY~Cs zuI0_Sjg54&cmE`xtZ`6@7I$ZM+YIUYQ$Bg$+v|f=L|WPk|11&qvq0oDT;jr3r0Haq zR7;b-&p4(FbJ~3>AwDgQrRHxI6~Z$HC>};6EOwYK=0inLhR@ENmQnNAiE^gTj@5bd zYU2Urt@k?xTZd%|Tf+f*aGP>+HSUoBL}qNh(bzvXk7?*=%=ocP*zrNcj~GIDE(1`t zPkN~MIfT~p=R@(Vyz5sd`OnPFKRI+tt|m*n_VANxC@aS~yb8^trDX>(P>Am*lWwr`8ok zCFA=Zr1kUSz`x8^U^2h}Tc=m&?Qecret$nv`Rb>0VF}J0Uk#_B$fnDxnwXQ&x2`V! zn7fid7aQfkyD~UN^3j?oo2>QDc$fMOWSdu7YiF!Z7pV*xyj!+Y_2T^)OCN?O>Uvw0 z2LR1@<5t@4jee7>!ZPcy;0y|F`J1FCN?q@zugJYf5u>;E3G*^b!jb`^F1L!d*A1%u)t zGJvnz`w~L{5VvGu7wW&E?Ebiojeia|BnNv0C1nA6wdmnSD~ULblZxm11fl~i^cDy_ z9DK67Zu2>soIkBs;(}J}2F^@W!u z`kdJaTsON-S%?S;XJ~ubC9SQkEmykT4m)rU*4Hvg zH&R>p675d+pss!5V|)^kb3kM`1X8%S)gd|jJz}pqKQdi7-?04En`RzLg0Gzn_GtMjeR4neYCRc-dDrY(oL zW`6Ob;j0Y4L6aoh{b1x6(08An2QdoXFo(v!bSkL=L**HbVP5X;3fmv#3>#is?aoZC ztIlrHpBENdPgWI$VBL*i<>E5FAX2xt_wN^^d>#ItN;IXUasP1z!&@{c9v(}PgUI~Fo~;1Cv=qR!)KTiFYm4tgyPXFOcu z`vtWa)kHRQ&pT*(K?>EWUh&`-uD%89(YS@MVLFIC$^(byvv(3_%K@FLRBg!+(Sw6N z$zWCSBTqlRW+&y&Tg>PGU~fHmEUc_UK1;^_XH61RUp_9sEm5H5;CSPIkou^z>3Dc} z+53?Y9GJ=*U@9z?F(mL!{DOjlTU1&J0J~XU2hIiM<%LEMaby_rh>4ZDD0$yTN&2(g zg5udZUwPaL?!_9}0amQ1XePrHkv@yU!t7OjSo)QgWOo|g{NNg27W1_K5#vAktR^h6 zL9S0+*I6QM@LZllBj4l2@g3dEw>dlbWSQhE-|;N9zM` zK&d9jpOq)F_k$T71!=8Z&q&5LDWJooBu24M$WRp0OWfGN~p* z8K$a?JReb~Pt9+A`f^#&tdjcFda631y2LNExmvu`Au~2szVS15sYl<|Bs~)O9zX~d=rpDYdyX}V(csb(ll z_)6%Mx2^j9_0;V%qvn{sVh%4;aJpKTQOhW8M(gy_T1!wr&RN-X! zTs2180Z)H!baXl1qLO-V`-O;{{9 zv(61(p0(i31~izV4P(Iq5qmzT`1E%M9RhrK+;(S$)d|uxZ|k${P{C)ST&2iaABXT} zojMKjGz=)--W{mXmOtnsu!uI~2*QJiNbS@%wn%68XPwOo`3!p@Gv!qQ;Qh?!z)&>y6d5!@(>D z!umEx`Zen|*?hVE{M0UDkDwA{Y>B(ep{>Wj&Zd^ueofB>NRkc>`dSGi zumKPc`mEBI#tQ0TKv*Kxqx>9Pb59(ajy}KCmodas{{XoeJClsgx&u5O|DPB6D;Ak5 zW7qROoBm6;9YZ77rSaO0SP(g|x{w_ayu^d|3xgnty?~6H3&cKvNRsBh zyOH~!t(>^-naIsJPy(;`Lk)Rz032cPW%y&6Aera6_}4ko24O#==g*LT~|vvr-{-&oU{1Vc)&1?Ihk*eUD7BF&yx0#8200XqYI zLIwt;O>l_(h*r>T&0DXdOQrg9=`lZEnY77FeWZXhebH@|;WNS`umTz)Ig5+w0#Bsf z0kve9w@uZ-L1!{;O?_q|SEZg7f4{)o6lo4{6Cbqw93%1C2A-IMhkug8%qMD{Dq08C ziGcdq!W|=Hda`9XoShTLe&mzzfg})!3_4i9{@Z1zp!Z_i_#HR!DzJ|W2L(t9WlvHUR<0X=wL|~78GD5lQ4sK0SLNO z03dPP;*uE)KYzw#wMq`P_~f&#(a+bKok5-d2u%0KTG2yd;slV0uv>l$?`e*sX#bb% zapwMfH4KrB+~&?+qt&cV`jgTYqdOLyF6^Q9w?Z>{j1+Y-4rm%?ctfc%D0*0I;4zTG z|HfJOu0D_%xO5-1`kQQx&Lr-iGk!S4@%t9e;r|tV86+M+jE*tLKc$ABJl4e^IR6bl zRNH-+qEX!K|J8k*Lmd3SS%-wg?rq#$k97wc#%ML}@Kkp|CA0&90g2AV!mYFt?zyHcb7H^(-`4!IKNk1XMOY>?P zS6~rCA-UN};`aZ=oMH?x)TVjBEPSDg;OaR@t zYy6*j&m*9R{n2H6NBGor7n8nt)VG}c+uZ4=Krb*+bchKhx_x+F1!iTn-{Kv)&*c5} zS=&DV^)$V@p`o15N#d9vC=*?M_vWFCYI5IDyWQPbg(W4U658h;Dk}U{miW^6_?Pd7 zTXM3qL2E!~>KTvD&FWG}2t#a zy4RWgWL-tF@)Hm1OheEGG!T9Mn9_o=OtZ9bQSDR4RcoMMrA4%t637lg&8nrWs+toY z@47t{r(gYhxc8HlAcEsJFFgrdOjhbmR5}5UI2&7>r^I=NIEL78BP|w!l1^R2DW=+C zPWjt61AgY_NHB}Sv}4{tb-Ehpz;FuEnNSg@T93?(jL*U+0`VUwqV%h*^N$W!W>d;l zy(mwkAP(K_{!+(TAu<;9&RkSDhR3UaXs&8&8vT5LF|KNA47{ERG}!iPK=IGn5|KI# zNESWk8t~crGjC9{BLyHnE1c%y>$h{*6Y9(W4$peLo93qT1I0YJ+S5QjMb;usYYQL8 zw4X^SJ#rn}ib|XF`bBX1`qf(RgR2Vb5rV69G124W?FRJ*cRl@G7JU+JR2VL1!TzK+ zT^Q#LLV@PGFPHQq#wXCmQ}@5tzlwF+zOb1fUP~n&{U{k5tC3>1`&3VFrfSwl;a>TN znN#XvT%4I;`LFbGYe%_hs^w|?I7?pLBy)w<$ zY&agk7(Y{^Q2e6+D|~S?Aai2274kcSp|$C6P|%C+Uj`^#$AjD$&b!~lPu#W=H)yC8 zlhS9xMBM*`8#VlLk8L_XJd~Z#9zN3ifhsapXVfq5ieIjqo2D>saAyV0f-$o}ujiE1 zBYl~lo~)^#>|7lgiT1q%(RBdcFGH#_yt4oDWpz6B1IUOvXpnzR0@)1 z)cWPg%{EgyZwwW*i`;~!*%hLvg@=7Vbh=ErxCD%(dq-2Fac}nsPY)*jrV`c;dWK_| z5QtyMH|x0Z)8&iTIk6+4Ih$sec2R(tb=hm016B$y)? zZz1065V;%)4jCDudmBAl)R~WrnO1|6T5HoGcI$zi>3w1dr~`c`Z{8}^SJ#fS~SHkxxZQy z=EWpmpNYHbq|mgOj8*9$EHxyy?#xVC%{Dq5`E}X!Elgd&JvU7>2AIenEfU!&eAAgF zy-yIDfK*0Xy#M9)N!?E;tY5euAHY4!h+9%QIKIVyU$ye$LZ6DE$)?vo>}r6ZX8?yNMuLWYu(66+=u`5hZkFe=kUe{yHF z3zrzN7fKsO{k7xcFH47JC9{k2bo^n^OX94%($pj@kh}XcA;dcrE@1ychSx%M;Fx93 zA^8seHFN8+xoAZb$jn=du<~*{**`Tpiy!{@f#~_M`4GK8%f?=Q0#r}y>`{L&bl_*0 zW$9jTgsVk^yn&}*dRKbFhF(=EZmTy!-BB={73sR-HhvZiGWc;L@zjd%Y({Gf{Uf$p z-IhJ<5q}lk4+rBPc}dAlTu<}{oN)fQbsHw_592T>tnt90 ztiRmWSHccBGZ)ao(Hz|GVeet@cDj&?Nd>sbFS|LBZ5)pA(&eV7RA?V@bxL~&JeK41 z6xLryLw3i%Z^NZkVHz5-lkJb4oY^e06XJ{_*O&`EPhJn=g!*9yG$o*r4 zJGgW90Z_N>qr%r-PgoSeP{wpdts&iMlGMwW5C0w+e#^4OO^=L>-MO~g7fZf1lh5Vl z?Jdeu&VBgv$DP|)!R+D|DOu>xj+{i%f@E;(KhmO-5MT7GlHGS6?#C&{$PvJ%OKSb- zPCpH|IY!bZ=lfbV@IOC!;xW*j5D}HuiVx-hTel;o$bxPV1((a(R$(RO z3s)zC;tY>=v#K2za7)Apwr3hdn@$($C*x3(&rD5m{LWTH`Fu}lTe1&xryo6fba=WH zFg&_&qi3*ZPUd&9jo~_BPWntsOG|fUMPX;Uw`|5*_v_uyPgl0Q|7jsSW;rj50imRS z`KRQ$y0e>HZ;v7M@rIm|(!0YS^e6qD??^ReWjlQ4%gdd~h4%P*+Lr#dywy3oJP7zy zRFcx4TTo>)HpEeL`Nsd!$^L^7v)Vmsv2f6^-+D#NV!$29UUFZquvx*u(~aQX=+?JS zdEaeF*GX<0$y7{^AaOZA8ap#xd1N9*V;3{51tt@pqzJi?FbE63tM}L?(E0eP#CE>5 zwf6%VfPh60_c}b#D}yzk);2jc$n0T2n=|rr8BS$%F>~qWu|^9M0du z-%Nhv_bqBm_u2UfGHV)*wO%UhBY=;wUR@r*Iy_}V>XG9no;Uu^!_&&2L%>=B|VG>FD^kLox_~z`%SB4NL4g8Pc0xCBr;Yh zs#*-YnZ4V%Z5MDY|A5|6jagLDETr*_DnHX7S*u`4w0|Ht zw2^Q8*r1w-oYCpX_r(j!o|Gf9NSc7eB4^#tmkVK;D2q(L9zJ%-6pQ@XJQemK*9Q;k zW}Gf$_9UPq7M!fR{n`$Y*_((s0Y6KN@#Es=vsHGh!CarGUxBtpsY-Y?RxjHIT8RkK zpZuA4^6nkx;c9B%dd~MAd)4Xqp@JRBn=^;Qei=0ixve#yqfL3u{ALx<6RRR9`EJl?z8Y={k9oNOUiTS4WBUk>p9y(i zc)|G*C62hbSg?)Q1@2z0Nn>qSo=RQR6R+cm{2`Y-nW%!x)2j~L8zr%@nV+rOco989 zO5^?$h0*Vx&7!)xx-8K$hhO}dyPoShBN#0)-1KAwz4jbHE!oh9I*tD+x=OO+hw8O+ z>8=%g4e5MUsuZG*sAy8_jp2;~CMP3_M}Fr|Mb^^Tt5h_P?}vM}W71cS-Cd1d$L0;g z!;d26w=3 zb)&>Fbcjuk*eRA|dIHdHwyu!H*{Pi9SsUH!X1|N0*b!0pQDE_oWUHSvh;M>Az%xMJe&NnL#NNT0$mP47_mVDQ&!JO{_@uYjc zA2#vz>C)#;m&_}2&s+~z*YzRpvnCgzL=EDf2{k26f6AwxPGLE8rNq!)Kkf@&s=Zyb z6{Vs#-zzom=+oIMWj`E0b_kJpmWG+s#p7%9`3tFO8^dAbn9%*jGtSt27-k|l< zHaf97maiR*wanU08Z@kBC@qhjtlBpDUwIFF?ZWwL`@^MISjJ%V82;;Qru%f2dDm-7@Gz}DZmXRSjtw?_)a3Rh*q{``-9?v*x*q@e5ve-C`T znvWd(UBXd8nt8q|!ot@^R}l(@5#*O{Tg8OfdS1doCmMoSVtYp=4JCr%)xNXuj;;0X zzu{d>*q~eLK+#+i?%m;Ntd$J%f#Xn%Wo^IGdH4dkRoe2q@^=@tPaEItR;-m3>*Rjr zF-^_brm*d}6ghX6+P2!9FV~CHyM6~f(V_cBo(m3dGhe;EH*Him^PpCNmJUi`J2Dt1 zQ_6_)Tl%UZexH+aXIz7&adde26}jZ~!wzRJ)&b)HL%aLCVQ*6%GFW^|UGSrKXG~5O zBOJ(ee(er&wOo3H{?(yRv#ilE*LXx@ltleigz_ApWb%A$oT7<3s*FZtKy>Y-?R9jv zvA>t|_z{sF-L6Bg%a&Z2)6#omjkhIFUU~7pzLW(W3vv=GH~mc0K>ZNH?a?n1o%{^Q zu}=z~ttc8()KUM}Eq!rHQ%TpwcFXDm0MbouY?WGs8mh$-U!V2W;FlV5ioCM$$nH%S z4d)bixYBg7!yI~a_-A6o)s(K|JrllarNM_yypgvMk(Ua{+cg6fc8+vwomkG>fBed# z4!Crb(U!P$4Bucg3nddLXbZkd^9*@WG`qKeW`pZ@1XL(9bthE7e){v{Bm5crH(2>n zrt5F19`W3=J5Jg}9pOl$qeYqPcX5m;PV}JSol^R9bL~*yf8K65KBOjTS(s_)b$+~U zRfBlGPf9{E5QHuq>XzyB-LgZ79e>)s?&?9U48$xm$}vnvvi-5~v?LFK;wByDl&P23 zyS@7F@5!umUpxK9NfGt#32zNU7=OumiRnO+oQWA1|8h9p=eW z#j{r&5v60StczotC+u9FrQV4)1KDqm6-aL+%8iJH_-N-l(f=l!-Juwk=W|)59O^0u zY~!_+5~NL&SL(^w|8OzYvxjZktix9G1S!fXlxZth~G(PQqv3+e#ObjCItltUjVnJ3_H}-d7ON z)nDnyN`DipH4>~8q4buaPOcw)t5EB|Gh@J6SvcF&kUCq}m)Rm~>9@f4r*cSsfacoK zU$^kUr7yp}K81us|MicJ^_*`}xOg-E*yswfXfV%!SqMhk?U;G&PNZkZ)*6VgAv)i1 zW>`R`+~1wB^*&qNCnHm&@p9kH^n*iRMw4mLuXo{zoQ9^lwxvch_m;lMzO`@Bd8Pgc z*Lxki7$Q!E$xDpOclIr&oy%K=+!#%$GjM!ozm@((UabAd<;MT3_#qC} zd#brm?6ZYuPjtmejf8q#F{ez}&JTx5*9(hwu$Vq)FiFnM83dOYJJ_xzC*NPxK9+q0 z(3A)^td$TcNc1#-l)-3zFhFcTW4dHO@6t5|vdh82Q)x3cSyqcp5w!b>oP^*zB17W% z^5BE*$Pb@T$-P!hJ`km`+%KieK6lMQd!v(W& zgH=Z;OnfQe`an+|{h>iMk4gQPFF4@D*c#HwNJLMoa>ms zW>E)9&^)MX2bwJ8S6HZADI@5P1d}Ju`c$SXYALRQf8F!k+Y>q7Og0`RZH6ZcY+uxy z<)BeB_G-4k-U!ZmrpyN1Xe*4y;A&-#xJ>6MKZ&$ZV_=S$YLIrls4A>bKI5h>`CPTo zpUTtLS_^raCc#)AMo4OLIhmW=>FHlS`d%}({?fwpc{a_^f(fzqfD{! zx5IUmU=`47O{U~}66Cu%RXN%Fg4@9u-`#bes5cM^0up= zs30At9`!rucKX0HV{mfOccPSw}&6CHK(#H4Nd4PKG7GbtiBKf$MT~u~?2XjlTZM`yJ}JxioImXLB$L$TOf&r=Sqw z#tkiO`|J&uJxK)P`Rb*Ipw`F(Pt)$Kohm3R^N3&kkx}XOSqaf=;IW}cbvx*&6`0fc9l*IMSh?wi4qIRBQO_VF zH1p+79AQm2#R=jfBK61pE_01tXXzeBW(Fm|rYd3oM$*A_DX5Y^Sh7#Wr3 z5UUOW7n)b=ce>2FGM+YS7n91^+P+8WzP9V+{vaK)er%(TtxCg$L^p>CqCZ`{`&%v` zy4f!EltUc5Q1jZ=)%700MxWousEBa$ZTyQAHihXvT>nN&Gq5^3*|0EP??y4>*m0Fd zu*c$F!EN6CYH`D_5%VeczS#M?AD`KGp^68F?|3OFC#F?YSYR#}atIozm_6=qa&f>` z^hvRFp`)lm;^Zl@mAV zdso5v#o>+daZi+7>QO&0&)}O;#%yltgq!(bk8#e;cg#k4Gy!Q-w!)VzhOV_&$3=3a zRz;2A=%C$-!cs=Xgo<$iafE#fYP3|}`>-GF-&Mh=45w;3iJ$azvtK@{03ht--|L9m zTzVTkc3&2qXZok8fwJPIx_PCYO(UPZIyzhtoI>Q@oUGDB-uF=Dx3H)XASmqok6IuP zk}w(E-){y72~++57{ZG2E`w&m0_1ZDk0;iLGM^R3O3TWk*ZME(rr z`=s|q;)0k!__xoYQBts)n6my6#dbTx@uM;&?QVs4?&}nS1!_zHx|fN^E14& z&Go`TLO$c6hQ%tm8~?hR&tCBjzw+;B88CY|v;IX?^5Td$vExr@UwCEqR4Fl;*3aOr z@~@v3z_B+IlQc|<0S&QeK?w;B-LK!iiCT?uF1f+)V2kMM8oFaG&hE$1T|FdqI++RJ zAK_JRdvF59)oLcSPdxqJ{u@=Q_klvu^~qZ8%uDKeM%P3hQw=gRTgUb9w)6ce<3jie z8JzBibpywl4;yAYMzaq=%OG`Qn61Lw{}kX^mri@plQzEy>g+7-J|4BCH=7n1Ck;I6 zix8ZWkzlr{(-@8Ei?FYuyPKsO`u?7nFh7qlWj#LGwirp$qrfGp`1QL>paFmH6F3&T z5bCt>zDz_r@?@_0pf`-;9XMxZ#%f&lv5~v)2KZ&N@CJuDfBib=bW`5~{Ol>af2>5@ z)hwa|MNxsOt+iBEeJq%yY|iEBr#qnwsb=&YviLC*;|yFFh1R*Yx%k6Xw}5U#~^ zwOzx^1KX7ct2O5k2r1L;-vW@lIvkiBs?zP?X})q#G<{Kk|4(TU5(W{`T}@l)?>RFu zdBN)|t59Rl;f`K$bG7RugCB-VBimRIkxDmw)XC1yz5zhD8jk&6Tx!*Zp&HP$v()$5 z?ap9EC-0(qOy~EnCQjWL&5NO9kFC+~MFSI@l<0hPNT`!a44)7*Y!;$0L{m#|+uiw- zH=8R4)JA4(J0X}moZ95woAzIRtYzLr3pn8T%L&o1%mv)stb?4NTy9FF#dSm3;B5Pq z{YW8Wx_ips#%mBD7-NHBa&ySZ0mCOsex4PN6 zc&gUGOb^{ta{K&gBm8y+a7xZ))Ny^)x03(+QsrIQSG=F`IKm;Ni=!p!<6ZNOe1#hG zRCT?Q0bevKm(~vU1q2#Ayo&L_fy1Cl@+tv8mpOCu71voW!i{it`+T{GM1dr+0?!Nz zE@!xxdIGg7(e2sl7cTEiN&L$nxx79w_(e|A&k{7nTJt<)4{v^p+c+BkJ*F&~0$nKP z9ucMgZ%r*NELzhC{U{kyHkL90M)sJuphSIFJ$AZ1>6+9UoV3$;pnpCe%=(z-+nuwMJ%WYva{?eCLLwO0+wl*}k1G@r-ycwRYvn7TNM=Hk7 zE`t5ZkLWjg1T!BMpM!!s<+~`E*H6YZYr$Z4r1$rd^bgBh**sK`6 zU#=*nH(nN;@$>fC+s%!?jW$La+i_2N@3PQwX?xFlnaNDfGnhz|sO|gwHK9BQL&dsZ z=xS}nM6OaD{P~{#78Dm-osZX4MI?t}qyc!m2D+3c@w4f`XSZpfEF~BNmq652qwO8D zQueD4Rg~{7tY^zTm%+Y`3b;YBKNv3U%VolBW96*rJB)d^)2~*zYOHp5X6jn5Hj8%O zo~#Mn{SyODkiSY~c<2NMnJsnu06?YyX0-X>p{qcFgMh zMPX_GZv!jz%k)IN(h{%ZdgMF!X{2ZeM4eJ%f8rM5zO?zfgz!{>Ge~lKjVzi!dsgTNR7%Q(>YBRdeWY%seJ?<4yux(j^UYa&b2Z)I;m}&nq~aZ={NxwfcVWN%(tsudpYX)j zx;>)1zK^#??YQ6L^8bU9h3pYXbrpKeI;3q`Z>P9Q;L0wZpy_BBH8anjIc8D<%p;i1%kbnnMU^G^6bL^5OcompjJ#6 zyjF6vWxP9jg|pV?HfC6;#?M#y!2y|^vmO|tTB$%!<#>&NHcCpixBo%l%)D1^68b{- z0$2V%uI97Bje?wdtm4?Q{pRCIAIrXsR)4Mn=rTj){*RFK|# z5eO|v5qLrLo4~iue{jxPCu{$-_u6~%%*>uW&&++@6smxRn@?)bBJX-thH`zK$#HuW zyKT6TyRrh$nS%TES53Pu>F0Jq8v0~njN=ScPYs;e#@W3(&@55~`0*H0JZ26tHR3zc z`0ux)ov}FflXQS+VrG3-HHU7JBD<%%_wCL$j>FXT6u4((rHlccv zk3%SPa;kD-IU6X&6L3w3D%sfC<1bay7VzZTjWR{-!E*+m_6I-x`T(ZHYvXp|wLB>6 zRiG9sG`PIvg9AGX>B!Q5UBf||y5Dj2ZS~(rA8bdU-gv=TPZTzpiM96B4XWW94z~VV z-N4i}_N^?fpt)Z@8&d2ica+)H*KhH5O(tq();#x&FJQuVcn0W54wmz^ZKAUtG4|U^FXNlD@%`d%DkxO>A`S>Otq$l~!dE-0cf}5CYu@ za%U1zp*NkMKMxLQ(|EskFmF;!6tA6kT1@EZX`Au3DLpjH&d&b+MuEO7bO4YL`iph* zJ3EUm&)KK~z@q9}VZL?lYKFIc3<7Mfe3#o!Re5TkJQr|iym%orjbeJXc7~@`n}~EV z#GWP`q9h6kaWjPk8E6Aqb}6%%ucHssU^WHW?|iE5V&lJ~aC|ZifHUnMlA`f0@__A4 zVzGA-R`kXf{OX}c6X`(D*j|R>FLr1DP-$tpT8x_?%r%kFh4w%f zN@s%9g|~bw%xTQq>(9!5#YGOKL#qDN6|bv%kgo!=r|*TIU|_FSKVsl{vOfC8Tv=Ao zv^gBH_&KV2;1r#r|Bs#8P+mZ(*pS~xzV2T4lSKsT=2WV4WULV2?IGpTrXBMehxfnb z<4uq-O#g5nAEloss>B{otV!X@u?RenFj!-=y}5}u0aw?emFE6o?1%NqUqk3 zMlKy`O<7`M3AZuR$a{zKn=3CjO0r@kc|pME-R?H_QPL&BzWV)QE1Gy;4a@b|-iA6S z4|O{n;_nVAcA`~vExwju9N9E38U)QvX=^4M7#)KtLBC6Fn)x_tiU})0c1W3%FH|S- z63q|(?tmHcHv_L^_?tbx0XjbY0r5Y*O}gpIempU3UgE`O<(>Dag8xGjoZ5wzN1n`AW+I|nePkp_CB@V9b#!uD zLk(Q3o?7!Fh70!gnWm5nZ#r#TLpYqjyY7WKvaPnd*pWyyA3bfkJ=^_aA4i2tFrB@` zBC*bo6@r^m%yI`Q;IbxocI!9b{=!w)o$_ZteNsOM) zthGPd!dBuy&Nrx?IEME>=(sx9?=?2FF*z^Q#fT_>n66UmIK=Esm(}Ylk?isg;`76& z{`7#{5qx)Jbw=wv44iuvOkR30x<`c3unn_z!^2yOH1&ITQ)mZ8Gd#yilx2K&(ce6N z(g%T6GUu?ezUnaB$R>W~$2)Yv`}=xq(kmR1(K3O7+k6Q*w7()nwT#SDpwf4KQ-C28 zR%Pzw6mpPHUPD{1kdh|R*2U?Ks+n!xiz=4?Kyk@Cj0npPS9M(}nU^byFRc_2p+xOO zIOq_i7m6?pNz>7ZRYA36ZW!Md*AV3AcWj8m#J1{*mmPW}C>_O2UB+^OUA6g*>ozWQ zAppUID1EGtXIZljZ!;!OPTq1n`uSE_fZ^qiEHPYS8cy3tc;(cwlpMaeo-(qc3r`om zwVjw8ZU;WE{Qy%|0eXp-Q&b^xl$lG6^;SSZVvp*0a$Sj62Nkyt?v~ zKlLBLP-zijlI@f;?Or=31=EnS(3J2PPqV7wVdSaeM!=mW0Ultp;5UC7tLbXnYqBxy zcfi58l468Oq$oRG=xKUP=kdTb;O43wBULzDBQJRm`Duun8?6Z}^BEP`h zg0Mf~16C>=^*1eSF|cRT6dwFyAs$i|rPiUsDk^cz;=gChXX-8Q(cG8gZHeln=2S!} zdaVx#UsP?)E}StgpnC%Bg0Q>B+m_MM5nH{W{@4# zvt7NqWw2oN4y3Or;=!TvtkBE;CH66qk$K>i&$<<=-;yU@V$q&X^t(B2>&wC82Hqnu zTCR>dhkN5jCmcza=pFY~FtR#oCK9ys3GF~@p}V&dpqrc9(8x{ym?BtBN2ebDWoXbm z>^0S$CIXdP{{%F1?-kOe=Vg25CWwpqY3{)_md{m7UtRRkqbO?{hr6x_zV>Ghqr;D0 zMnQl%iYrcb+(+u!7hJn%Puj0=fJNKXYO{*WCWI#PV+U zQe1;qds~);eSS<%Og0X88;Hs)j4oC9#u3pJEppZ&hxZpdDjimPovj&-MUu^Hp2I@P zGt}1Bl6BUK-&9CR86A7w7rjm#1LudRM=e#l_sDZY(7itsj@xt|ojTZeI z1_WeTVA~q4yA`-|Byy)OowQIJO^j^B2YOu0C5B+h!@f$=an$pq%=5ZxvvLTr-YpMZ z8?qPn+VZW!504dFXaS|cje->sPNXX@+S=Y8!p^=bGQk~P2@rv;oiG@-i-vE;d3Vna zfwX4SwPsEISY6%R z^hC~Ef_aCqoOhTC?~A{GnqHWEWtvFlLtAc7R-)n5g5=APOMeYU7{M>l11Cjw!_(7{$%^N5gT$lkjD#6&=$U02CupzsaL zk@pZZeMEG0zpP%{TUO{wFboSb=)a`z*IC`EZ-xnkBzkKM@M~k62Ac{s~9fF%O z_+19oM?zs(^jYEi{TC$f6;mJ(U_Adtvte!IX6W(LqOcbEvAO3u6e%HL-7Y7z+2W%x z+S7xWJgD5NqFa~xkW6=ci9bjie_-2|HDzBw6Uksm9cc(S%XY+;;u=?H3wv99;`V5o zU%5(4?4hHC=u|OAl=7KDy5H=bJB0vu&x=vhA5#&#DU@D_(QUs`bO~!JbZ_;R8^t*g zmA@Jr89AI^nh^i+N(E7xusUB$tc-$!)!$+AyXSKHXBidkbIZrayGn1;RhgK=o9n!7 ztgMOyKJ2@^_}pi3ftUA)p)k-xB6ZGduL}BSg{Cw${q<|lqqjwYb~+CNsOHBhNt8|# zOSAu5qG@8PseA~mv^|(KoTAKQ86q7>tER?Gf}9rbe8W=Bi{hKjrdo`61f>7Nd_e*> z9pgZ1LaiMzcJr2I77KxfFpMP9s*REYWD!fOIgPTB;T_?N1X6&2xQKUxg!_^FVq61v zEa9vgm<-dtDhI2fEU}Z#-v{E8A~5gC7Br*)Mi2?|0zn&9x=jF3Vf)HT$dqJ1z=BE7 zfD1wq4jdnj!CK6e*Z|Ng3QIUNL~|66<%tE>6}NhPQdc|R{B}a0s#aI zunlEnx^@nj07UJIm2xV~?z|Q#0ez+GaIXMRgrExk$A0mgis1*0pm>&17CnJX2@3Kh z;CBD_g`RnI8W}h{Q2)0f+zEzd;(qGv=OJi<5Ja7j1;E0S(27?9ens%EI&UUM(1;}k z3&3~gECfps!g7UB74ccU8J?q>;gjd}<__vDu{*v0tW&G08G(9>WP>=JGe%#44a5Jh bLjQ1CYmm`i6|3_j0>3+&`Whwb4@3R~?c9zu literal 28277 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 IQsxo=2UchWWdHyG diff --git a/mobile-app-new/pubspec.lock b/mobile-app-new/pubspec.lock index 382a82bd5..5f6ef82f1 100644 --- a/mobile-app-new/pubspec.lock +++ b/mobile-app-new/pubspec.lock @@ -767,10 +767,10 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc" + sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.8" flutter_riverpod: dependency: "direct main" description: diff --git a/mobile-app-new/pubspec.yaml b/mobile-app-new/pubspec.yaml index 2afde6cdc..ceba6d099 100644 --- a/mobile-app-new/pubspec.yaml +++ b/mobile-app-new/pubspec.yaml @@ -96,6 +96,8 @@ dev_dependencies: json_serializable: ^6.14.1 go_router_builder: ^4.4.0 riverpod_lint: 3.1.4 + flutter_launcher_icons: 0.14.4 + flutter_native_splash: 2.4.8 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From ce213504a442afbbaee452738104c2d530333755 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Wed, 21 Oct 2026 17:36:40 +0530 Subject: [PATCH 50/52] fix: bug fixes from PR comments --- .../lib/news/controllers/feed_controller.dart | 9 +++-- .../news/controllers/feed_controller.g.dart | 2 +- .../lib/news/ui/search/search_viewmodel.dart | 13 +++++++- .../news/ui/search/search_viewmodel.g.dart | 2 +- .../controllers/player_controller.dart | 2 +- .../controllers/player_controller.g.dart | 2 +- .../podcasts/services/library_service.dart | 5 +++ .../episode_list/episode_list_viewmodel.dart | 9 +++-- .../episode_list_viewmodel.g.dart | 2 +- .../lib/services/audio_service.dart | 33 ++++++++++++++++++- 10 files changed, 68 insertions(+), 11 deletions(-) diff --git a/mobile-app-new/lib/news/controllers/feed_controller.dart b/mobile-app-new/lib/news/controllers/feed_controller.dart index a7fb12b51..c654ddee4 100644 --- a/mobile-app-new/lib/news/controllers/feed_controller.dart +++ b/mobile-app-new/lib/news/controllers/feed_controller.dart @@ -28,8 +28,12 @@ abstract class NewsFeedState with _$NewsFeedState { @riverpod class NewsFeedNotifier extends _$NewsFeedNotifier { + // NOTE: A refresh keeps this notifier mounted, so a page fetched before it is told apart by this. + int _generation = 0; + @override FutureOr build(NewsFeedSource source) async { + _generation++; final page = await _fetch(ref.watch(newsApiServiceProvider), ''); return NewsFeedState( @@ -57,6 +61,7 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { // A failed append waits for an explicit retry instead of refiring on scroll. if (current.loadMoreError != null && !isRetry) return; + final generation = _generation; state = AsyncData( current.copyWith(isLoadingMore: true, loadMoreError: null), ); @@ -66,7 +71,7 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { ref.read(newsApiServiceProvider), current.cursor, ); - if (!ref.mounted) return; + if (!ref.mounted || generation != _generation) return; state = AsyncData( current.copyWith( @@ -77,7 +82,7 @@ class NewsFeedNotifier extends _$NewsFeedNotifier { ), ); } catch (error) { - if (!ref.mounted) return; + if (!ref.mounted || generation != _generation) return; // Keep the loaded posts; only the append failed. state = AsyncData( current.copyWith(isLoadingMore: false, loadMoreError: error), diff --git a/mobile-app-new/lib/news/controllers/feed_controller.g.dart b/mobile-app-new/lib/news/controllers/feed_controller.g.dart index 77bfcc7e7..c86587f4d 100644 --- a/mobile-app-new/lib/news/controllers/feed_controller.g.dart +++ b/mobile-app-new/lib/news/controllers/feed_controller.g.dart @@ -50,7 +50,7 @@ final class NewsFeedNotifierProvider } } -String _$newsFeedNotifierHash() => r'e6505957c249efe2462ada429b29cac1820e9d51'; +String _$newsFeedNotifierHash() => r'a485ea16eae289dee3a26758570aeaf736d47e0b'; final class NewsFeedNotifierFamily extends $Family with diff --git a/mobile-app-new/lib/news/ui/search/search_viewmodel.dart b/mobile-app-new/lib/news/ui/search/search_viewmodel.dart index 1151545f3..ef8141e5d 100644 --- a/mobile-app-new/lib/news/ui/search/search_viewmodel.dart +++ b/mobile-app-new/lib/news/ui/search/search_viewmodel.dart @@ -6,13 +6,17 @@ part 'search_viewmodel.g.dart'; @riverpod class NewsSearchNotifier extends _$NewsSearchNotifier { + // NOTE: Algolia drops a query identical to its last one, so a repeat is answered from here. + String? _lastTerm; + List? _lastResults; + @override AsyncValue> build() { final subscription = ref .watch(newsSearchServiceProvider) .results .listen( - (results) => state = AsyncData(results), + (results) => state = AsyncData(_lastResults = results), onError: (Object error, StackTrace stackTrace) => state = AsyncError(error, stackTrace), ); @@ -28,6 +32,13 @@ class NewsSearchNotifier extends _$NewsSearchNotifier { return; } + if (term == _lastTerm && _lastResults != null) { + state = AsyncData(_lastResults!); + return; + } + + _lastTerm = term; + _lastResults = null; state = const AsyncLoading(); ref.read(newsSearchServiceProvider).query(term); } diff --git a/mobile-app-new/lib/news/ui/search/search_viewmodel.g.dart b/mobile-app-new/lib/news/ui/search/search_viewmodel.g.dart index b8d809643..ca54445dc 100644 --- a/mobile-app-new/lib/news/ui/search/search_viewmodel.g.dart +++ b/mobile-app-new/lib/news/ui/search/search_viewmodel.g.dart @@ -43,7 +43,7 @@ final class NewsSearchNotifierProvider } String _$newsSearchNotifierHash() => - r'c9774ff6d3b50f833b36425af89b45ee16143109'; + r'9a71f563daa8cfb10e82dda064b58242bbdd5522'; abstract class _$NewsSearchNotifier extends $Notifier>> { diff --git a/mobile-app-new/lib/podcasts/controllers/player_controller.dart b/mobile-app-new/lib/podcasts/controllers/player_controller.dart index fa585ae88..c9997f734 100644 --- a/mobile-app-new/lib/podcasts/controllers/player_controller.dart +++ b/mobile-app-new/lib/podcasts/controllers/player_controller.dart @@ -129,7 +129,7 @@ class PodcastPlayerNotifier extends _$PodcastPlayerNotifier { if (loaded) await handler.play(); } finally { - if (ref.mounted) { + if (ref.mounted && state.loadingEpisodeId == episode.id) { state = state.copyWith(loadingEpisodeId: null); _syncKeepAlive(); } diff --git a/mobile-app-new/lib/podcasts/controllers/player_controller.g.dart b/mobile-app-new/lib/podcasts/controllers/player_controller.g.dart index 5ed18fa27..f7b02d2d9 100644 --- a/mobile-app-new/lib/podcasts/controllers/player_controller.g.dart +++ b/mobile-app-new/lib/podcasts/controllers/player_controller.g.dart @@ -77,7 +77,7 @@ final class PodcastPlayerNotifierProvider } String _$podcastPlayerNotifierHash() => - r'6fcf9fed68135e6e8a3a8dbb7c472f2759f826e6'; + r'd416e8d5664fa7e15b7d048126086d700024ba51'; abstract class _$PodcastPlayerNotifier extends $Notifier { PodcastPlayerState build(); diff --git a/mobile-app-new/lib/podcasts/services/library_service.dart b/mobile-app-new/lib/podcasts/services/library_service.dart index 4b38c6943..a2340b0e5 100644 --- a/mobile-app-new/lib/podcasts/services/library_service.dart +++ b/mobile-app-new/lib/podcasts/services/library_service.dart @@ -50,6 +50,11 @@ class PodcastLibraryService { await _files.saveArtwork(podcast.id, podcast.imageLink); + if (cancelToken?.isCancelled ?? false) { + await _files.deleteEpisodeFile(episode.podcastId, episode.id); + return; + } + await _repo.upsertEpisode( podcast: podcast.toJson(), episode: episode.toJson(), diff --git a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart index 3575172a7..b20cb431f 100644 --- a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart +++ b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart @@ -33,8 +33,12 @@ abstract class PodcastEpisodesState with _$PodcastEpisodesState { class PodcastEpisodesNotifier extends _$PodcastEpisodesNotifier { Podcast? _lastKnownPodcast; + // NOTE: A refresh keeps this notifier mounted, so a page fetched before it is told apart by this. + int _generation = 0; + @override FutureOr build(PodcastEpisodeSource source) async { + _generation++; switch (source) { case RemoteEpisodes(:final podcastId): final page = await ref @@ -81,6 +85,7 @@ class PodcastEpisodesNotifier extends _$PodcastEpisodesNotifier { if (current.loadMoreError != null && !isRetry) return; if (source case RemoteEpisodes(:final podcastId)) { + final generation = _generation; state = AsyncData( current.copyWith(isLoadingMore: true, loadMoreError: null), ); @@ -91,7 +96,7 @@ class PodcastEpisodesNotifier extends _$PodcastEpisodesNotifier { final page = await ref .read(podcastApiServiceProvider) .getEpisodes(podcastId, page: next); - if (!ref.mounted) return; + if (!ref.mounted || generation != _generation) return; state = AsyncData( current.copyWith( @@ -102,7 +107,7 @@ class PodcastEpisodesNotifier extends _$PodcastEpisodesNotifier { ), ); } catch (error) { - if (!ref.mounted) return; + if (!ref.mounted || generation != _generation) return; state = AsyncData( current.copyWith(isLoadingMore: false, loadMoreError: error), ); diff --git a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart index 859b82cbc..5a8c7e138 100644 --- a/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart +++ b/mobile-app-new/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart @@ -53,7 +53,7 @@ final class PodcastEpisodesNotifierProvider } String _$podcastEpisodesNotifierHash() => - r'6504648d1bc6d72209b4b6c958eb81418bacc2a4'; + r'74e02ef2905e26c0bdcfca2a69a8e2d7fc132f94'; final class PodcastEpisodesNotifierFamily extends $Family with diff --git a/mobile-app-new/lib/services/audio_service.dart b/mobile-app-new/lib/services/audio_service.dart index 205f83db9..f258efafb 100644 --- a/mobile-app-new/lib/services/audio_service.dart +++ b/mobile-app-new/lib/services/audio_service.dart @@ -79,6 +79,14 @@ class AudioPlayerHandler extends BaseAudioHandler { // itself on interruptions and unplugging, so the flag follows playingStream. bool _resumeNeedsLiveEdge = false; + // NOTE: Every load takes a new id, so a load interrupted by a newer one + // returns without stopping, restoring or publishing over the newer source. + int _loadId = 0; + + // NOTE: While a source is swapping, position events can still come from the + // old one, so no episode is reported until the new one is in place. + bool _loading = false; + String? get codeRadioSongId => _audioConfig is CodeRadioAudioConfig ? mediaItem.value?.id : null; @@ -86,7 +94,7 @@ class AudioPlayerHandler extends BaseAudioHandler { _audioPlayer.playing && _audioConfig is CodeRadioAudioConfig; String? get episodeId => switch (_audioConfig) { - PodcastAudioConfig(:final episodeId) => episodeId, + PodcastAudioConfig(:final episodeId) when !_loading => episodeId, _ => null, }; @@ -119,6 +127,8 @@ class AudioPlayerHandler extends BaseAudioHandler { @override Future stop() async { + _loadId++; + _loading = false; _audioConfig = null; _codeRadioUrl = null; _resumeNeedsLiveEdge = false; @@ -173,7 +183,9 @@ class AudioPlayerHandler extends BaseAudioHandler { } Future loadCodeRadio(CodeRadio radio) async { + final load = ++_loadId; final previous = _audioConfig; + _loading = true; try { final song = _toMediaItem(radio.nowPlaying.song); @@ -185,11 +197,16 @@ class AudioPlayerHandler extends BaseAudioHandler { await _audioPlayer.setSpeed(_defaultSpeed); await _audioPlayer.setAudioSource(AudioSource.uri(url)); + if (load != _loadId) return; _publish(song); } catch (e) { + if (load != _loadId) return; + _audioConfig = previous; log('loadCodeRadio: Cannot play audio: $e'); + } finally { + if (load == _loadId) _loading = false; } } @@ -221,6 +238,8 @@ class AudioPlayerHandler extends BaseAudioHandler { artUri: artUri, ); + final load = ++_loadId; + _loading = true; _codeRadioUrl = null; _resumeNeedsLiveEdge = false; _audioConfig = PodcastAudioConfig(episode.id); @@ -232,13 +251,18 @@ class AudioPlayerHandler extends BaseAudioHandler { if (startAt != null && startAt > Duration.zero) { await _audioPlayer.seek(startAt); } + if (load != _loadId) return false; _publish(item); return true; } catch (e) { + if (load != _loadId) return false; + log('loadEpisode: Cannot play audio: $e'); await stop(); return false; + } finally { + if (load == _loadId) _loading = false; } } @@ -251,6 +275,8 @@ class AudioPlayerHandler extends BaseAudioHandler { }) async { final item = MediaItem(id: clipId, title: title, album: 'freeCodeCamp'); + final load = ++_loadId; + _loading = true; _codeRadioUrl = null; _resumeNeedsLiveEdge = false; _audioConfig = CurriculumAudioConfig(clipId); @@ -264,13 +290,18 @@ class AudioPlayerHandler extends BaseAudioHandler { child: AudioSource.uri(source), ), ); + if (load != _loadId) return false; _publish(item); return true; } catch (e) { + if (load != _loadId) return false; + log('loadCurriculumAudio: Cannot play audio: $e'); await stop(); return false; + } finally { + if (load == _loadId) _loading = false; } } From 0dae06be70e9cc548405270ecb60e026948d8ce2 Mon Sep 17 00:00:00 2001 From: Niraj Nandish Date: Fri, 23 Oct 2026 00:31:57 +0530 Subject: [PATCH 51/52] fin --- .github/workflows/flutter-checks.yml | 9 +- .github/workflows/flutter-deploy.yml | 15 + .lintstagedrc.json | 5 +- AGENTS.md | 25 +- codemagic.yaml | 16 +- docs/LAYER_MAP.md | 4 +- mobile-app-new/.gitignore | 45 - mobile-app-new/.metadata | 33 - mobile-app-new/README.md | 17 - mobile-app-new/analysis_options.yaml | 33 - mobile-app-new/android/.gitignore | 14 - mobile-app-new/android/app/build.gradle.kts | 58 - .../android/app/google-services.json | 29 - .../android/app/src/debug/AndroidManifest.xml | 7 - .../android/app/src/main/AndroidManifest.xml | 78 - .../mobile_app_new/MainActivity.kt | 5 - .../res/drawable-hdpi/notification_icon.png | Bin 759 -> 0 bytes .../res/drawable-mdpi/notification_icon.png | Bin 479 -> 0 bytes .../res/drawable-v21/launch_background.xml | 12 - .../res/drawable-xhdpi/notification_icon.png | Bin 1080 -> 0 bytes .../res/drawable-xxhdpi/notification_icon.png | Bin 1691 -> 0 bytes .../drawable-xxxhdpi/notification_icon.png | Bin 2300 -> 0 bytes .../main/res/drawable/launch_background.xml | 12 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 0 bytes .../app/src/main/res/values-night/styles.xml | 18 - .../app/src/main/res/values/styles.xml | 18 - .../main/res/xml/network_security_config.xml | 6 - .../app/src/profile/AndroidManifest.xml | 7 - mobile-app-new/android/build.gradle.kts | 24 - mobile-app-new/android/gradle.properties | 6 - .../gradle/wrapper/gradle-wrapper.properties | 5 - mobile-app-new/android/settings.gradle.kts | 30 - mobile-app-new/assets/fonts/Hack-Regular.ttf | Bin 305172 -> 0 bytes mobile-app-new/assets/fonts/Lato-Regular.ttf | Bin 120196 -> 0 bytes mobile-app-new/assets/icon/icon.png | Bin 35043 -> 0 bytes mobile-app-new/assets/images/apple-logo.png | Bin 54493 -> 0 bytes .../assets/images/freecodecamp-banner.png | Bin 159847 -> 0 bytes mobile-app-new/assets/images/github-logo.png | Bin 6393 -> 0 bytes mobile-app-new/assets/images/google-logo.png | Bin 122573 -> 0 bytes mobile-app-new/assets/images/logo.png | Bin 25521 -> 0 bytes .../assets/images/placeholder-profile-img.png | Bin 22460 -> 0 bytes mobile-app-new/assets/learn/a1-chinese.svg | 8 - mobile-app-new/assets/learn/a1-spanish.svg | 8 - mobile-app-new/assets/learn/a2-english.svg | 8 - mobile-app-new/assets/learn/algorithm.svg | 3 - mobile-app-new/assets/learn/analytics.svg | 3 - mobile-app-new/assets/learn/api.svg | 3 - mobile-app-new/assets/learn/b1-english.svg | 8 - mobile-app-new/assets/learn/c-sharp.svg | 3 - mobile-app-new/assets/learn/clipboard.svg | 3 - mobile-app-new/assets/learn/code.svg | 3 - .../assets/learn/college-algebra.svg | 3 - mobile-app-new/assets/learn/d3.svg | 3 - mobile-app-new/assets/learn/database.svg | 3 - mobile-app-new/assets/learn/graduation.svg | 3 - mobile-app-new/assets/learn/javascript.svg | 3 - .../assets/learn/motivational-quotes.json | 716 ------ mobile-app-new/assets/learn/python.svg | 3 - mobile-app-new/assets/learn/react.svg | 3 - .../assets/learn/responsive-design.svg | 3 - mobile-app-new/assets/learn/rosetta-code.svg | 3 - mobile-app-new/assets/learn/shield.svg | 3 - mobile-app-new/assets/learn/tensorflow.svg | 3 - mobile-app-new/assets/learn/viking-helmet.svg | 3 - mobile-app-new/assets/test_runner/VERSION.txt | 1 - .../assets/test_runner/babel/babel.min.js | 2 - .../dist/126.217b2b5c2726a434eadb.js | 1 - .../dist/214.992f651a9fe4ee5dc553.js | 1 - .../dist/26.d4b48aa36b1383e317ab.js | 2 - .../26.d4b48aa36b1383e317ab.js.LICENSE.txt | 68 - .../dist/302.c8ab63712f1603f9d24f.js | 1 - .../dist/325.090d1ef9d7ecde370904.js | 6 - .../dist/325.713e5e15bda3843ab320.js | 6 - .../dist/361.dca39c129f300a617e4f.js | 2 - .../361.dca39c129f300a617e4f.js.LICENSE.txt | 10 - .../dist/512.963dc1e3a5deb572ac05.js | 1 - .../dist/670.0a71e5e6c744ab2b7403.js | 1 - .../dist/697.c1141ab556afb9c3f94b.js | 1 - .../dist/821.b35c74c0050a22ec9938.js | 1 - .../dist/836.487cb77f05dbe6f3a434.js | 1 - .../dist/943.99d1c181a2263104e4d0.js | 1 - .../test_runner/dist/dom-test-evaluator.js | 2 - .../dist/dom-test-evaluator.js.LICENSE.txt | 550 ----- .../enzyme-adapter.a8f1009fa72e3f86807e.js | 1 - .../dist/enzyme.a8f1009fa72e3f86807e.js | 1 - .../dist/explorer.45ddb7897528f130cbed.js | 1 - .../dist/explorer.59c927c94d2bdb7bd844.js | 1 - .../dist/explorer.767baf5fecabfbb23cf8.js | 1 - .../dist/explorer.7858a111e0df509ae645.js | 1 - .../assets/test_runner/dist/index.js | 1 - .../dist/javascript-test-evaluator.js | 2 - .../javascript-test-evaluator.js.LICENSE.txt | 539 ----- .../test_runner/dist/python-test-evaluator.js | 2 - .../dist/python-test-evaluator.js.LICENSE.txt | 539 ----- mobile-app-new/devtools_options.yaml | 3 - mobile-app-new/firebase.json | 1 - .../integration_test/news/bookmark_test.dart | 125 - mobile-app-new/integration_test_runner.dart | 230 -- mobile-app-new/ios/.gitignore | 34 - .../ios/Flutter/AppFrameworkInfo.plist | 24 - mobile-app-new/ios/Flutter/Debug.xcconfig | 1 - mobile-app-new/ios/Flutter/Release.xcconfig | 1 - .../ios/Runner.xcodeproj/project.pbxproj | 676 ------ .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/swiftpm/Package.resolved | 158 -- .../xcshareddata/xcschemes/Runner.xcscheme | 119 - .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/WorkspaceSettings.xcsettings | 8 - .../xcshareddata/swiftpm/Package.resolved | 158 -- mobile-app-new/ios/Runner/AppDelegate.swift | 18 - .../AppIcon.appiconset/Contents.json | 122 - .../Icon-App-1024x1024@1x.png | Bin 10932 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 0 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 0 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 0 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 0 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 0 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 0 bytes .../LaunchImage.imageset/Contents.json | 23 - .../LaunchImage.imageset/LaunchImage.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 68 -> 0 bytes .../LaunchImage.imageset/README.md | 5 - .../Runner/Base.lproj/LaunchScreen.storyboard | 37 - .../ios/Runner/Base.lproj/Main.storyboard | 26 - .../ios/Runner/GoogleService-Info.plist | 30 - mobile-app-new/ios/Runner/Info.plist | 87 - .../ios/Runner/Runner-Bridging-Header.h | 1 - mobile-app-new/ios/Runner/Runner.entitlements | 11 - mobile-app-new/ios/Runner/SceneDelegate.swift | 6 - .../ios/RunnerTests/RunnerTests.swift | 12 - mobile-app-new/lib/firebase_options.dart | 67 - mobile-app-new/lib/main.dart | 135 -- mobile-app-new/pubspec.lock | 2089 ----------------- mobile-app-new/pubspec.yaml | 154 -- .../test_driver/integration_test.dart | 18 - mobile-app/analysis_options.yaml | 12 +- mobile-app/android/app/build.gradle.kts | 1 - .../android/app/src/main/AndroidManifest.xml | 3 +- .../app/src/main/res/values/styles.xml | 5 +- mobile-app/android/gradle.properties | 1 - .../assets/database/bookmarked-article.db | Bin 16384 -> 0 bytes mobile-app/assets/database/podcasts.db | 0 mobile-app/assets/images/episode_default.jpg | Bin 82402 -> 0 bytes .../assets/images/freecodecamp-banner.png | Bin 43511 -> 159847 bytes mobile-app/assets/images/logo.jpg | Bin 18304 -> 0 bytes .../assets/images/placeholder-profile-img.png | Bin 28277 -> 22460 bytes mobile-app/assets/images/splash_screen.png | Bin 27124 -> 0 bytes mobile-app/assets/sql/1_create_db_schema.sql | 25 - mobile-app/assets/sql/2_delete_all_values.sql | 2 - .../assets/sql/3_reset_episodes_schema.sql | 13 - mobile-app/assets/sql/4_delete_all_values.sql | 2 - mobile-app/assets/test_data/news_feed.json | 1385 ----------- mobile-app/assets/test_data/news_post.json | 156 -- .../integration_test/learn/landing_test.dart | 10 +- .../integration_test/learn/learn_landing.dart | 66 - .../integration_test/news/bookmark_test.dart | 154 +- .../test_runner/curriculum_test.dart | 8 +- .../test_runner/curriculum_tests.dart | 183 -- .../test_runner/test_runner_view.dart | 118 - mobile-app/integration_test_runner.dart | 379 ++- .../xcshareddata/swiftpm/Package.resolved | 32 +- .../xcshareddata/swiftpm/Package.resolved | 36 +- mobile-app/l10n.yaml | 3 - mobile-app/lib/app/app.dart | 93 - mobile-app/lib/app/app.locator.dart | 65 - mobile-app/lib/app/app.logger.dart | 159 -- mobile-app/lib/app/app.router.dart | 1412 ----------- .../lib/auth/models/user_model.dart | 2 +- .../lib/auth/models/user_model.freezed.dart | 0 .../lib/auth/models/user_model.g.dart | 0 .../auth/repositories/session_repository.dart | 2 +- .../repositories/session_repository.g.dart | 0 .../lib/auth/services/session_service.dart | 4 +- .../lib/auth/services/session_service.g.dart | 0 .../lib/auth/ui/login/login_view.dart | 10 +- .../lib/auth/ui/login/login_viewmodel.dart | 4 +- .../ui/login/login_viewmodel.freezed.dart | 0 .../lib/auth/ui/login/login_viewmodel.g.dart | 0 .../lib/auth/ui/profile/profile_view.dart | 12 +- .../auth/ui/profile/profile_viewmodel.dart | 4 +- .../auth/ui/profile/profile_viewmodel.g.dart | 0 .../code_radio/models/code_radio_model.dart | 0 .../models/code_radio_model.freezed.dart | 0 .../code_radio/models/code_radio_model.g.dart | 0 .../repositories/websocket_repository.dart | 0 .../repositories/websocket_repository.g.dart | 0 .../services/code_radio_service.dart | 6 +- .../services/code_radio_service.g.dart | 0 .../lib/code_radio/ui/player/player_view.dart | 10 +- .../ui/player/player_viewmodel.dart | 8 +- .../ui/player/player_viewmodel.g.dart | 0 mobile-app/lib/constants/radio_articles.dart | 9 - mobile-app/lib/enums/alert_type.dart | 39 - mobile-app/lib/enums/dialog_type.dart | 7 - mobile-app/lib/enums/ext_type.dart | 27 - mobile-app/lib/enums/panel_type.dart | 1 - mobile-app/lib/enums/theme_type.dart | 17 - mobile-app/lib/extensions/i18n_extension.dart | 6 - .../lib/fcc_theme.dart | 0 mobile-app/lib/firebase_options.dart | 4 +- mobile-app/lib/l10n/app_en.arb | 1086 --------- mobile-app/lib/l10n/app_es.arb | 888 ------- mobile-app/lib/l10n/app_localizations.dart | 1940 --------------- mobile-app/lib/l10n/app_localizations_en.dart | 1058 --------- mobile-app/lib/l10n/app_localizations_es.dart | 1058 --------- mobile-app/lib/l10n/app_localizations_pt.dart | 1058 --------- mobile-app/lib/l10n/app_pt.arb | 888 ------- .../lib/learn/constants/curriculum.dart | 0 .../controllers/curriculum_controller.dart | 6 +- .../controllers/curriculum_controller.g.dart | 0 .../learn/controllers/scene_controller.dart | 8 +- .../controllers/scene_controller.freezed.dart | 0 .../learn/controllers/scene_controller.g.dart | 0 .../learn/models/challenge_file_model.dart | 0 .../models/challenge_file_model.freezed.dart | 0 .../learn/models/challenge_file_model.g.dart | 0 .../lib/learn/models/challenge_model.dart | 2 +- .../learn/models/challenge_model.freezed.dart | 0 .../lib/learn/models/challenge_model.g.dart | 0 .../lib/learn/models/curriculum_model.dart | 0 .../models/curriculum_model.freezed.dart | 0 .../lib/learn/models/curriculum_model.g.dart | 0 .../learn/models/daily_challenge_model.dart | 4 +- .../models/daily_challenge_model.freezed.dart | 0 .../learn/models/daily_challenge_model.g.dart | 0 .../lib/learn/models/scene_assets_model.dart | 0 .../models/scene_assets_model.freezed.dart | 0 .../learn/models/scene_assets_model.g.dart | 0 .../repositories/curriculum_repository.dart | 6 +- .../repositories/curriculum_repository.g.dart | 0 .../daily_challenge_repository.dart | 4 +- .../daily_challenge_repository.g.dart | 0 .../learn/repositories/prefs_repository.dart | 0 .../repositories/prefs_repository.g.dart | 0 .../learn/repositories/quote_repository.dart | 0 .../repositories/quote_repository.g.dart | 0 .../lib/learn/services/code_service.dart | 8 +- .../lib/learn/services/code_service.g.dart | 0 .../learn/services/curriculum_service.dart | 12 +- .../learn/services/curriculum_service.g.dart | 0 .../daily_challenge_reminder_service.dart | 8 +- .../daily_challenge_reminder_service.g.dart | 0 .../services/daily_challenge_service.dart | 8 +- .../services/daily_challenge_service.g.dart | 0 .../lib/learn/services/quote_service.dart | 2 +- .../lib/learn/services/quote_service.g.dart | 0 .../learn/services/test_runner_service.dart | 4 +- .../learn/services/test_runner_service.g.dart | 0 .../learn/ui/challenge/challenge_view.dart | 14 +- .../ui/challenge/challenge_viewmodel.dart | 16 +- .../ui/challenge/challenge_viewmodel.g.dart | 0 .../lib/learn/ui/challenge/english_view.dart | 24 +- .../lib/learn/ui/challenge/lesson_view.dart | 26 +- .../ui/challenge/python_project_view.dart | 14 +- .../lib/learn/ui/challenge/quiz_view.dart | 18 +- .../ui/challenge/widgets/assignment_tile.dart | 4 +- .../challenge/widgets/challenge_button.dart | 2 +- .../ui/challenge/widgets/challenge_card.dart | 2 +- .../ui/challenge/widgets/challenge_video.dart | 0 .../ui/challenge/widgets/complete_button.dart | 12 +- .../ui/challenge/widgets/example_editor.dart | 6 +- .../ui/challenge/widgets/help_dialog.dart | 4 +- .../learn/ui/challenge/widgets/html_card.dart | 4 +- .../ui/challenge/widgets/parsed_html.dart | 2 +- .../learn/ui/challenge/widgets/questions.dart | 10 +- .../challenge/widgets/quiz_audio_player.dart | 8 +- .../daily_challenge/daily_challenge_view.dart | 12 +- .../daily_challenge_viewmodel.dart | 4 +- .../daily_challenge_viewmodel.g.dart | 0 .../daily_challenge_archive_view.dart | 16 +- .../daily_challenge_archive_viewmodel.dart | 2 +- .../daily_challenge_archive_viewmodel.g.dart | 0 .../ui/editor_challenge/editor_body.dart | 6 +- .../editor_challenge_view.dart | 18 +- .../editor_challenge_viewmodel.dart | 10 +- .../editor_challenge_viewmodel.freezed.dart | 0 .../editor_challenge_viewmodel.g.dart | 0 .../ui/editor_challenge/editor_panels.dart | 20 +- .../ui/landing/daily_challenge_card.dart | 8 +- .../lib/learn/ui/landing/landing_view.dart | 26 +- .../learn/ui/landing/landing_viewmodel.dart | 6 +- .../learn/ui/landing/landing_viewmodel.g.dart | 0 .../learn/ui/landing/superblock_button.dart | 2 +- .../lib/learn/ui/module/module_view.dart | 8 +- .../scene_fullscreen_view.dart | 8 +- .../learn/ui/superblock/superblock_view.dart | 16 +- .../lib/learn/ui/widgets/block_card.dart | 12 +- .../learn/ui/widgets/scene/scene_canvas.dart | 6 +- .../ui/widgets/scene/scene_controls.dart | 8 +- .../learn/ui/widgets/scene/scene_view.dart | 14 +- mobile-app/lib/main.dart | 196 +- .../models/code-radio/code_radio_model.dart | 57 - .../lib/models/learn/challenge_model.dart | 729 ------ .../learn/completed_challenge_model.dart | 52 - .../lib/models/learn/curriculum_model.dart | 366 --- .../models/learn/daily_challenge_model.dart | 156 -- .../learn/motivational_quote_model.dart | 10 - .../models/learn/saved_challenge_model.dart | 20 - .../lib/models/learn/scene_assets_model.dart | 67 - .../lib/models/main/portfolio_model.dart | 25 - .../lib/models/main/profile_ui_model.dart | 38 - mobile-app/lib/models/main/user_model.dart | 293 --- .../news/bookmarked_tutorial_model.dart | 20 - .../lib/models/news/tutorial_model.dart | 149 -- .../lib/models/podcasts/episodes_model.dart | 94 - .../lib/models/podcasts/podcasts_model.dart | 68 - .../lib/news/constants.dart | 0 .../controllers/bookmarks_controller.dart | 4 +- .../controllers/bookmarks_controller.g.dart | 0 .../lib/news/controllers/feed_controller.dart | 4 +- .../controllers/feed_controller.freezed.dart | 0 .../news/controllers/feed_controller.g.dart | 0 .../lib/news/models/author_model.dart | 0 .../lib/news/models/author_model.freezed.dart | 0 .../lib/news/models/author_model.g.dart | 0 .../news/models/bookmarked_post_model.dart | 2 +- .../models/bookmarked_post_model.freezed.dart | 0 .../news/models/bookmarked_post_model.g.dart | 0 .../lib/news/models/post_model.dart | 2 +- .../lib/news/models/post_model.freezed.dart | 0 .../lib/news/models/post_model.g.dart | 0 .../lib/news/models/post_summary_model.dart | 4 +- .../models/post_summary_model.freezed.dart | 0 .../lib/news/models/post_summary_model.g.dart | 0 .../lib/news/models/search_post_model.dart | 0 .../models/search_post_model.freezed.dart | 0 .../lib/news/models/search_post_model.g.dart | 0 .../lib/news/repositories/api_repository.dart | 0 .../news/repositories/api_repository.g.dart | 0 .../repositories/bookmark_repository.dart | 0 .../repositories/bookmark_repository.g.dart | 0 .../news/repositories/search_repository.dart | 0 .../repositories/search_repository.g.dart | 0 .../lib/news/services/api_service.dart | 8 +- .../lib/news/services/api_service.g.dart | 0 .../lib/news/services/bookmark_service.dart | 4 +- .../lib/news/services/bookmark_service.g.dart | 0 .../lib/news/services/search_service.dart | 6 +- .../lib/news/services/search_service.g.dart | 0 .../news/ui/author_feed/author_feed_view.dart | 13 +- .../ui/author_feed/author_feed_viewmodel.dart | 4 +- .../author_feed/author_feed_viewmodel.g.dart | 0 .../ui/bookmark_feed/bookmark_feed_view.dart | 10 +- .../ui/bookmark_post/bookmark_post_view.dart | 10 +- .../lib/news/ui/feed/feed_view.dart | 4 +- .../lib/news/ui/image/image_view.dart | 2 +- .../lib/news/ui/news_shell.dart | 4 +- .../lib/news/ui/post/post_view.dart | 20 +- .../lib/news/ui/post/post_viewmodel.dart | 4 +- .../lib/news/ui/post/post_viewmodel.g.dart | 0 .../lib/news/ui/search/search_view.dart | 8 +- .../lib/news/ui/search/search_viewmodel.dart | 4 +- .../news/ui/search/search_viewmodel.g.dart | 0 .../lib/news/ui/tag_feed/tag_feed_view.dart | 6 +- .../news/ui/widgets/back_to_top_button.dart | 2 +- .../lib/news/ui/widgets/bookmark_button.dart | 8 +- .../lib/news/ui/widgets/bottom_button.dart | 2 +- .../lib/news/ui/widgets/post_feed_list.dart | 8 +- .../lib/news/ui/widgets/post_tile.dart | 10 +- .../news/ui/widgets/search_result_tile.dart | 8 +- .../lib/news/ui/widgets/tag_button.dart | 4 +- .../lib/podcasts/constants.dart | 0 .../controllers/downloads_controller.dart | 8 +- .../downloads_controller.freezed.dart | 0 .../controllers/downloads_controller.g.dart | 0 .../controllers/library_controller.dart | 4 +- .../controllers/library_controller.g.dart | 0 .../controllers/player_controller.dart | 8 +- .../player_controller.freezed.dart | 0 .../controllers/player_controller.g.dart | 0 .../lib/podcasts/models/episode_model.dart | 0 .../models/episode_model.freezed.dart | 0 .../lib/podcasts/models/episode_model.g.dart | 0 .../lib/podcasts/models/podcast_model.dart | 0 .../models/podcast_model.freezed.dart | 0 .../lib/podcasts/models/podcast_model.g.dart | 0 .../podcasts/repositories/api_repository.dart | 2 +- .../repositories/api_repository.g.dart | 0 .../repositories/download_repository.dart | 2 +- .../repositories/download_repository.g.dart | 0 .../repositories/progress_repository.dart | 0 .../repositories/progress_repository.g.dart | 0 .../repositories/storage_repository.dart | 0 .../repositories/storage_repository.g.dart | 0 .../lib/podcasts/services/api_service.dart | 8 +- .../lib/podcasts/services/api_service.g.dart | 0 .../podcasts/services/library_service.dart | 11 +- .../podcasts/services/library_service.g.dart | 0 .../podcasts/services/playback_service.dart | 12 +- .../podcasts/services/playback_service.g.dart | 0 .../podcasts/ui/downloads/downloads_view.dart | 10 +- .../lib/podcasts/ui/episode/episode_view.dart | 14 +- .../ui/episode_list/episode_list_view.dart | 14 +- .../episode_list/episode_list_viewmodel.dart | 8 +- .../episode_list_viewmodel.freezed.dart | 0 .../episode_list_viewmodel.g.dart | 0 .../ui/podcast_list/podcast_list_view.dart | 10 +- .../podcast_list/podcast_list_viewmodel.dart | 4 +- .../podcast_list_viewmodel.g.dart | 0 .../lib/podcasts/ui/podcasts_shell.dart | 4 +- .../podcasts/ui/widgets/download_button.dart | 10 +- .../lib/podcasts/ui/widgets/episode_tile.dart | 12 +- .../ui/widgets/podcast_grid_tile.dart | 4 +- .../lib/routing/auth.dart | 4 +- .../lib/routing/auth.g.dart | 0 .../lib/routing/code_radio.dart | 2 +- .../lib/routing/code_radio.g.dart | 0 .../lib/routing/learn.dart | 16 +- .../lib/routing/learn.g.dart | 0 .../lib/routing/news.dart | 22 +- .../lib/routing/news.g.dart | 0 .../lib/routing/podcasts.dart | 16 +- .../lib/routing/podcasts.g.dart | 0 .../lib/routing/router.dart | 12 +- .../lib/routing/settings.dart | 4 +- .../lib/routing/settings.g.dart | 0 .../lib/service/audio/audio_service.dart | 358 --- .../authentication_service.dart | 395 ---- mobile-app/lib/service/developer_service.dart | 8 - mobile-app/lib/service/dio_service.dart | 25 - .../service/firebase/analytics_observer.dart | 69 - .../service/firebase/analytics_service.dart | 18 - .../firebase/remote_config_service.dart | 120 - .../daily_challenge_notification_service.dart | 384 --- .../learn/daily_challenge_service.dart | 174 -- .../lib/service/learn/learn_file_service.dart | 340 --- .../service/learn/learn_offline_service.dart | 416 ---- .../lib/service/learn/learn_service.dart | 280 --- .../service/learn/scene_assets_service.dart | 54 - mobile-app/lib/service/locale_service.dart | 68 - .../navigation/quick_actions_service.dart | 72 - mobile-app/lib/service/news/api_service.dart | 295 --- .../lib/service/news/bookmark_service.dart | 230 -- .../news/legacy/bookmark_sqlite_migrator.dart | 40 - .../lib/service/podcast/download_service.dart | 84 - .../legacy/podcasts_sqlite_migrator.dart | 60 - .../service/podcast/notification_service.dart | 136 -- .../lib/service/podcast/podcasts_service.dart | 330 --- .../lib/service/storage/json_file_store.dart | 79 - .../lib/services/analytics_service.dart | 0 .../lib/services/analytics_service.g.dart | 0 .../lib/services/audio_service.dart | 8 +- .../lib/services/audio_service.g.dart | 0 .../lib/services/auth_service.dart | 6 +- .../lib/services/auth_service.g.dart | 0 .../lib/services/dio_service.dart | 0 .../lib/services/dio_service.g.dart | 0 .../lib/services/notification_service.dart | 2 +- .../lib/services/notification_service.g.dart | 0 .../lib/services/remote_config_service.dart | 0 .../remote_config_service.freezed.dart | 0 .../lib/services/remote_config_service.g.dart | 0 .../delete_account/delete_account_view.dart | 10 +- .../delete_account_viewmodel.dart | 4 +- .../delete_account_viewmodel.g.dart | 0 .../settings/ui/settings/settings_view.dart | 12 +- mobile-app/lib/ui/theme/fcc_theme.dart | 127 - .../ui/views/code_radio/code_radio_view.dart | 247 -- .../code_radio/code_radio_viewmodel.dart | 91 - .../learn/block/block_template_view.dart | 110 - .../learn/block/block_template_viewmodel.dart | 190 -- .../templates/dialogue/dialogue_view.dart | 258 -- .../dialogue/dialogue_viewmodel.dart | 3 - .../learn/block/templates/grid/grid_view.dart | 170 -- .../block/templates/grid/grid_viewmodel.dart | 3 - .../learn/block/templates/link/link_view.dart | 101 - .../block/templates/link/link_viewmodel.dart | 3 - .../learn/block/templates/list/list_view.dart | 180 -- .../block/templates/list/list_viewmodel.dart | 3 - .../views/learn/challenge/challenge_view.dart | 612 ----- .../learn/challenge/challenge_viewmodel.dart | 983 -------- .../templates/english/english_view.dart | 178 -- .../templates/english/english_viewmodel.dart | 231 -- .../multiple_choice/multiple_choice_view.dart | 243 -- .../multiple_choice_viewmodel.dart | 103 - .../python-project/python_project_view.dart | 180 -- .../python_project_viewmodel.dart | 68 - .../templates/python/python_view.dart | 197 -- .../templates/python/python_viewmodel.dart | 84 - .../challenge/templates/quiz/quiz_view.dart | 193 -- .../templates/quiz/quiz_viewmodel.dart | 126 - .../templates/review/review_view.dart | 153 -- .../templates/review/review_viewmodel.dart | 69 - .../challenge/templates/template_view.dart | 130 - .../templates/template_viewmodel.dart | 42 - .../learn/chapter/chapter_block_view.dart | 66 - .../chapter/chapter_block_viewmodel.dart | 32 - .../ui/views/learn/chapter/chapter_view.dart | 290 --- .../learn/chapter/chapter_viewmodel.dart | 196 -- .../daily_challenge/daily_challenge_view.dart | 276 --- .../daily_challenge_viewmodel.dart | 161 -- .../ui/views/learn/landing/landing_view.dart | 455 ---- .../learn/landing/landing_viewmodel.dart | 334 --- .../learn/superblock/superblock_view.dart | 169 -- .../superblock/superblock_viewmodel.dart | 123 - .../lib/ui/views/learn/test_runner.dart | 191 -- .../ui/views/learn/utils/challenge_utils.dart | 96 - .../ui/views/learn/utils/learn_globals.dart | 17 - .../learn/widgets/assignment_widget.dart | 71 - .../widgets/audio/audio_player_view.dart | 159 -- .../widgets/audio/audio_player_viewmodel.dart | 47 - .../views/learn/widgets/challenge_card.dart | 43 - .../views/learn/widgets/challenge_tile.dart | 58 - .../challenge_widgets/project_preview.dart | 54 - .../widgets/challenge_widgets/symbol_bar.dart | 65 - .../learn/widgets/console/console_view.dart | 76 - .../widgets/console/console_viewmodel.dart | 26 - .../learn/widgets/custom_alert_widget.dart | 45 - .../learn/widgets/daily_challenge_card.dart | 284 --- .../description/description_widget_model.dart | 3 - .../description/description_widget_view.dart | 101 - .../views/learn/widgets/example_editor.dart | 84 - .../learn/widgets/explanation_widget.dart | 34 - .../learn/widgets/hint/hint_widget_model.dart | 3 - .../learn/widgets/hint/hint_widget_view.dart | 129 - .../learn/widgets/pass/pass_widget_model.dart | 72 - .../learn/widgets/pass/pass_widget_view.dart | 259 -- .../learn/widgets/quiz_audio_player.dart | 224 -- .../ui/views/learn/widgets/quiz_widget.dart | 255 -- .../views/learn/widgets/scene/scene_view.dart | 551 ----- .../learn/widgets/scene/scene_viewmodel.dart | 565 ----- .../learn/widgets/transcript_widget.dart | 45 - .../learn/widgets/youtube_player_widget.dart | 44 - .../lib/ui/views/login/native_login_view.dart | 382 --- .../views/login/native_login_viewmodel.dart | 93 - .../views/news/html_handler/html_handler.dart | 338 --- .../news/news-author/news_author_view.dart | 126 - .../news-author/news_author_viewmodel.dart | 17 - .../news_bookmark_feed_view.dart | 67 - .../news-bookmark/news_bookmark_view.dart | 105 - .../news_bookmark_viewmodel.dart | 114 - .../news-bookmark/news_bookmark_widget.dart | 38 - .../views/news/news-feed/news_feed_view.dart | 263 --- .../news/news-feed/news_feed_viewmodel.dart | 122 - .../news-image-viewer/news_image_view.dart | 49 - .../news_image_viewmodel.dart | 3 - .../news/news-search/news_search_view.dart | 247 -- .../news-search/news_search_viewmodel.dart | 93 - .../news-tutorial/news_tutorial_view.dart | 236 -- .../news_tutorial_viewmodel.dart | 190 -- .../news_view_handler_view.dart | 65 - .../news_view_handler_viewmodel.dart | 10 - .../news/widgets/back_to_top_button.dart | 33 - .../lib/ui/views/news/widgets/tag_widget.dart | 91 - .../news/widgets/tutorial_list_widget.dart | 169 -- .../episode-list/episode_list_view.dart | 242 -- .../episode-list/episode_list_viewmodel.dart | 81 - .../views/podcast/episode/episode_view.dart | 255 -- .../podcast/episode/episode_viewmodel.dart | 226 -- .../podcast-list/podcast_list_view.dart | 219 -- .../podcast-list/podcast_list_viewmodel.dart | 65 - .../podcast/widgets/podcast_title_widget.dart | 422 ---- .../lib/ui/views/profile/profile_view.dart | 684 ------ .../ui/views/profile/profile_viewmodel.dart | 8 - .../delete-account/delete_account_view.dart | 111 - .../delete_account_viewmodel.dart | 102 - .../lib/ui/views/settings/settings_view.dart | 96 - .../ui/views/settings/settings_viewmodel.dart | 60 - .../ui/widgets/drawer_widget/drawer_tile.dart | 52 - .../drawer_widget/drawer_web_buttton.dart | 82 - .../drawer_widget/drawer_widget_view.dart | 178 -- .../drawer_widget_viewmodel.dart | 173 -- .../lib/ui/widgets/setup_dialog_ui.dart | 709 ------ .../lib/utils/custom_tab.dart | 0 .../lib/utils/date_utils.dart | 0 mobile-app/lib/utils/helpers.dart | 41 - .../lib/utils/riverpod.dart | 0 mobile-app/lib/utils/upgrade_controller.dart | 6 - .../lib/widgets/dialog/confirm_dialog.dart | 2 +- .../lib/widgets/drawer/drawer.dart | 24 +- .../lib/widgets/drawer/drawer_tile.dart | 0 .../lib/widgets/drawer/scroll_shadow.dart | 0 .../lib/widgets/error_retry.dart | 0 .../widgets/html_handler/html_handler.dart | 4 +- .../lib/widgets/section_divider.dart | 0 mobile-app/pubspec.lock | 861 +++---- mobile-app/pubspec.yaml | 75 +- mobile-app/test_driver/integration_test.dart | 36 +- 594 files changed, 1434 insertions(+), 44223 deletions(-) delete mode 100644 mobile-app-new/.gitignore delete mode 100644 mobile-app-new/.metadata delete mode 100644 mobile-app-new/README.md delete mode 100644 mobile-app-new/analysis_options.yaml delete mode 100644 mobile-app-new/android/.gitignore delete mode 100644 mobile-app-new/android/app/build.gradle.kts delete mode 100644 mobile-app-new/android/app/google-services.json delete mode 100644 mobile-app-new/android/app/src/debug/AndroidManifest.xml delete mode 100644 mobile-app-new/android/app/src/main/AndroidManifest.xml delete mode 100644 mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt delete mode 100644 mobile-app-new/android/app/src/main/res/drawable-hdpi/notification_icon.png delete mode 100644 mobile-app-new/android/app/src/main/res/drawable-mdpi/notification_icon.png delete mode 100644 mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml delete mode 100644 mobile-app-new/android/app/src/main/res/drawable-xhdpi/notification_icon.png delete mode 100644 mobile-app-new/android/app/src/main/res/drawable-xxhdpi/notification_icon.png delete mode 100644 mobile-app-new/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png delete mode 100644 mobile-app-new/android/app/src/main/res/drawable/launch_background.xml delete mode 100644 mobile-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 mobile-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 mobile-app-new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 mobile-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 mobile-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 mobile-app-new/android/app/src/main/res/values-night/styles.xml delete mode 100644 mobile-app-new/android/app/src/main/res/values/styles.xml delete mode 100644 mobile-app-new/android/app/src/main/res/xml/network_security_config.xml delete mode 100644 mobile-app-new/android/app/src/profile/AndroidManifest.xml delete mode 100644 mobile-app-new/android/build.gradle.kts delete mode 100644 mobile-app-new/android/gradle.properties delete mode 100644 mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties delete mode 100644 mobile-app-new/android/settings.gradle.kts delete mode 100644 mobile-app-new/assets/fonts/Hack-Regular.ttf delete mode 100644 mobile-app-new/assets/fonts/Lato-Regular.ttf delete mode 100644 mobile-app-new/assets/icon/icon.png delete mode 100644 mobile-app-new/assets/images/apple-logo.png delete mode 100644 mobile-app-new/assets/images/freecodecamp-banner.png delete mode 100644 mobile-app-new/assets/images/github-logo.png delete mode 100644 mobile-app-new/assets/images/google-logo.png delete mode 100644 mobile-app-new/assets/images/logo.png delete mode 100644 mobile-app-new/assets/images/placeholder-profile-img.png delete mode 100644 mobile-app-new/assets/learn/a1-chinese.svg delete mode 100644 mobile-app-new/assets/learn/a1-spanish.svg delete mode 100644 mobile-app-new/assets/learn/a2-english.svg delete mode 100644 mobile-app-new/assets/learn/algorithm.svg delete mode 100644 mobile-app-new/assets/learn/analytics.svg delete mode 100644 mobile-app-new/assets/learn/api.svg delete mode 100644 mobile-app-new/assets/learn/b1-english.svg delete mode 100644 mobile-app-new/assets/learn/c-sharp.svg delete mode 100644 mobile-app-new/assets/learn/clipboard.svg delete mode 100644 mobile-app-new/assets/learn/code.svg delete mode 100644 mobile-app-new/assets/learn/college-algebra.svg delete mode 100644 mobile-app-new/assets/learn/d3.svg delete mode 100644 mobile-app-new/assets/learn/database.svg delete mode 100644 mobile-app-new/assets/learn/graduation.svg delete mode 100644 mobile-app-new/assets/learn/javascript.svg delete mode 100644 mobile-app-new/assets/learn/motivational-quotes.json delete mode 100644 mobile-app-new/assets/learn/python.svg delete mode 100644 mobile-app-new/assets/learn/react.svg delete mode 100644 mobile-app-new/assets/learn/responsive-design.svg delete mode 100644 mobile-app-new/assets/learn/rosetta-code.svg delete mode 100644 mobile-app-new/assets/learn/shield.svg delete mode 100644 mobile-app-new/assets/learn/tensorflow.svg delete mode 100644 mobile-app-new/assets/learn/viking-helmet.svg delete mode 100644 mobile-app-new/assets/test_runner/VERSION.txt delete mode 100644 mobile-app-new/assets/test_runner/babel/babel.min.js delete mode 100644 mobile-app-new/assets/test_runner/dist/126.217b2b5c2726a434eadb.js delete mode 100644 mobile-app-new/assets/test_runner/dist/214.992f651a9fe4ee5dc553.js delete mode 100644 mobile-app-new/assets/test_runner/dist/26.d4b48aa36b1383e317ab.js delete mode 100644 mobile-app-new/assets/test_runner/dist/26.d4b48aa36b1383e317ab.js.LICENSE.txt delete mode 100644 mobile-app-new/assets/test_runner/dist/302.c8ab63712f1603f9d24f.js delete mode 100644 mobile-app-new/assets/test_runner/dist/325.090d1ef9d7ecde370904.js delete mode 100644 mobile-app-new/assets/test_runner/dist/325.713e5e15bda3843ab320.js delete mode 100644 mobile-app-new/assets/test_runner/dist/361.dca39c129f300a617e4f.js delete mode 100644 mobile-app-new/assets/test_runner/dist/361.dca39c129f300a617e4f.js.LICENSE.txt delete mode 100644 mobile-app-new/assets/test_runner/dist/512.963dc1e3a5deb572ac05.js delete mode 100644 mobile-app-new/assets/test_runner/dist/670.0a71e5e6c744ab2b7403.js delete mode 100644 mobile-app-new/assets/test_runner/dist/697.c1141ab556afb9c3f94b.js delete mode 100644 mobile-app-new/assets/test_runner/dist/821.b35c74c0050a22ec9938.js delete mode 100644 mobile-app-new/assets/test_runner/dist/836.487cb77f05dbe6f3a434.js delete mode 100644 mobile-app-new/assets/test_runner/dist/943.99d1c181a2263104e4d0.js delete mode 100644 mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js delete mode 100644 mobile-app-new/assets/test_runner/dist/dom-test-evaluator.js.LICENSE.txt delete mode 100644 mobile-app-new/assets/test_runner/dist/enzyme-adapter.a8f1009fa72e3f86807e.js delete mode 100644 mobile-app-new/assets/test_runner/dist/enzyme.a8f1009fa72e3f86807e.js delete mode 100644 mobile-app-new/assets/test_runner/dist/explorer.45ddb7897528f130cbed.js delete mode 100644 mobile-app-new/assets/test_runner/dist/explorer.59c927c94d2bdb7bd844.js delete mode 100644 mobile-app-new/assets/test_runner/dist/explorer.767baf5fecabfbb23cf8.js delete mode 100644 mobile-app-new/assets/test_runner/dist/explorer.7858a111e0df509ae645.js delete mode 100644 mobile-app-new/assets/test_runner/dist/index.js delete mode 100644 mobile-app-new/assets/test_runner/dist/javascript-test-evaluator.js delete mode 100644 mobile-app-new/assets/test_runner/dist/javascript-test-evaluator.js.LICENSE.txt delete mode 100644 mobile-app-new/assets/test_runner/dist/python-test-evaluator.js delete mode 100644 mobile-app-new/assets/test_runner/dist/python-test-evaluator.js.LICENSE.txt delete mode 100644 mobile-app-new/devtools_options.yaml delete mode 100644 mobile-app-new/firebase.json delete mode 100644 mobile-app-new/integration_test/news/bookmark_test.dart delete mode 100644 mobile-app-new/integration_test_runner.dart delete mode 100644 mobile-app-new/ios/.gitignore delete mode 100644 mobile-app-new/ios/Flutter/AppFrameworkInfo.plist delete mode 100644 mobile-app-new/ios/Flutter/Debug.xcconfig delete mode 100644 mobile-app-new/ios/Flutter/Release.xcconfig delete mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.pbxproj delete mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 mobile-app-new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 mobile-app-new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme delete mode 100644 mobile-app-new/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 mobile-app-new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 mobile-app-new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings delete mode 100644 mobile-app-new/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 mobile-app-new/ios/Runner/AppDelegate.swift delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png delete mode 100644 mobile-app-new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md delete mode 100644 mobile-app-new/ios/Runner/Base.lproj/LaunchScreen.storyboard delete mode 100644 mobile-app-new/ios/Runner/Base.lproj/Main.storyboard delete mode 100644 mobile-app-new/ios/Runner/GoogleService-Info.plist delete mode 100644 mobile-app-new/ios/Runner/Info.plist delete mode 100644 mobile-app-new/ios/Runner/Runner-Bridging-Header.h delete mode 100644 mobile-app-new/ios/Runner/Runner.entitlements delete mode 100644 mobile-app-new/ios/Runner/SceneDelegate.swift delete mode 100644 mobile-app-new/ios/RunnerTests/RunnerTests.swift delete mode 100644 mobile-app-new/lib/firebase_options.dart delete mode 100644 mobile-app-new/lib/main.dart delete mode 100644 mobile-app-new/pubspec.lock delete mode 100644 mobile-app-new/pubspec.yaml delete mode 100644 mobile-app-new/test_driver/integration_test.dart delete mode 100644 mobile-app/assets/database/bookmarked-article.db delete mode 100644 mobile-app/assets/database/podcasts.db delete mode 100644 mobile-app/assets/images/episode_default.jpg delete mode 100644 mobile-app/assets/images/logo.jpg delete mode 100644 mobile-app/assets/images/splash_screen.png delete mode 100644 mobile-app/assets/sql/1_create_db_schema.sql delete mode 100644 mobile-app/assets/sql/2_delete_all_values.sql delete mode 100644 mobile-app/assets/sql/3_reset_episodes_schema.sql delete mode 100644 mobile-app/assets/sql/4_delete_all_values.sql delete mode 100644 mobile-app/assets/test_data/news_feed.json delete mode 100644 mobile-app/assets/test_data/news_post.json rename {mobile-app-new => mobile-app}/integration_test/learn/landing_test.dart (88%) delete mode 100644 mobile-app/integration_test/learn/learn_landing.dart rename {mobile-app-new => mobile-app}/integration_test/test_runner/curriculum_test.dart (94%) delete mode 100644 mobile-app/integration_test/test_runner/curriculum_tests.dart delete mode 100644 mobile-app/integration_test/test_runner/test_runner_view.dart mode change 100755 => 100644 mobile-app/integration_test_runner.dart delete mode 100644 mobile-app/l10n.yaml delete mode 100644 mobile-app/lib/app/app.dart delete mode 100644 mobile-app/lib/app/app.locator.dart delete mode 100644 mobile-app/lib/app/app.logger.dart delete mode 100644 mobile-app/lib/app/app.router.dart rename {mobile-app-new => mobile-app}/lib/auth/models/user_model.dart (98%) rename {mobile-app-new => mobile-app}/lib/auth/models/user_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/auth/models/user_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/auth/repositories/session_repository.dart (98%) rename {mobile-app-new => mobile-app}/lib/auth/repositories/session_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/auth/services/session_service.dart (95%) rename {mobile-app-new => mobile-app}/lib/auth/services/session_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/auth/ui/login/login_view.dart (97%) rename {mobile-app-new => mobile-app}/lib/auth/ui/login/login_viewmodel.dart (94%) rename {mobile-app-new => mobile-app}/lib/auth/ui/login/login_viewmodel.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/auth/ui/login/login_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/auth/ui/profile/profile_view.dart (98%) rename {mobile-app-new => mobile-app}/lib/auth/ui/profile/profile_viewmodel.dart (65%) rename {mobile-app-new => mobile-app}/lib/auth/ui/profile/profile_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/code_radio/models/code_radio_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/code_radio/models/code_radio_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/code_radio/models/code_radio_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/code_radio/repositories/websocket_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/code_radio/repositories/websocket_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/code_radio/services/code_radio_service.dart (91%) rename {mobile-app-new => mobile-app}/lib/code_radio/services/code_radio_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/code_radio/ui/player/player_view.dart (95%) rename {mobile-app-new => mobile-app}/lib/code_radio/ui/player/player_viewmodel.dart (86%) rename {mobile-app-new => mobile-app}/lib/code_radio/ui/player/player_viewmodel.g.dart (100%) delete mode 100644 mobile-app/lib/constants/radio_articles.dart delete mode 100644 mobile-app/lib/enums/alert_type.dart delete mode 100644 mobile-app/lib/enums/dialog_type.dart delete mode 100644 mobile-app/lib/enums/ext_type.dart delete mode 100644 mobile-app/lib/enums/panel_type.dart delete mode 100644 mobile-app/lib/enums/theme_type.dart delete mode 100644 mobile-app/lib/extensions/i18n_extension.dart rename {mobile-app-new => mobile-app}/lib/fcc_theme.dart (100%) delete mode 100644 mobile-app/lib/l10n/app_en.arb delete mode 100644 mobile-app/lib/l10n/app_es.arb delete mode 100644 mobile-app/lib/l10n/app_localizations.dart delete mode 100644 mobile-app/lib/l10n/app_localizations_en.dart delete mode 100644 mobile-app/lib/l10n/app_localizations_es.dart delete mode 100644 mobile-app/lib/l10n/app_localizations_pt.dart delete mode 100644 mobile-app/lib/l10n/app_pt.arb rename {mobile-app-new => mobile-app}/lib/learn/constants/curriculum.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/controllers/curriculum_controller.dart (77%) rename {mobile-app-new => mobile-app}/lib/learn/controllers/curriculum_controller.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/controllers/scene_controller.dart (97%) rename {mobile-app-new => mobile-app}/lib/learn/controllers/scene_controller.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/controllers/scene_controller.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/challenge_file_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/challenge_file_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/challenge_file_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/challenge_model.dart (99%) rename {mobile-app-new => mobile-app}/lib/learn/models/challenge_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/challenge_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/curriculum_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/curriculum_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/curriculum_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/daily_challenge_model.dart (92%) rename {mobile-app-new => mobile-app}/lib/learn/models/daily_challenge_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/daily_challenge_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/scene_assets_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/scene_assets_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/models/scene_assets_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/curriculum_repository.dart (91%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/curriculum_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/daily_challenge_repository.dart (91%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/daily_challenge_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/prefs_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/prefs_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/quote_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/repositories/quote_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/services/code_service.dart (95%) rename {mobile-app-new => mobile-app}/lib/learn/services/code_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/services/curriculum_service.dart (93%) rename {mobile-app-new => mobile-app}/lib/learn/services/curriculum_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/services/daily_challenge_reminder_service.dart (90%) rename {mobile-app-new => mobile-app}/lib/learn/services/daily_challenge_reminder_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/services/daily_challenge_service.dart (90%) rename {mobile-app-new => mobile-app}/lib/learn/services/daily_challenge_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/services/quote_service.dart (89%) rename {mobile-app-new => mobile-app}/lib/learn/services/quote_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/services/test_runner_service.dart (98%) rename {mobile-app-new => mobile-app}/lib/learn/services/test_runner_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/challenge_view.dart (82%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/challenge_viewmodel.dart (84%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/challenge_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/english_view.dart (87%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/lesson_view.dart (81%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/python_project_view.dart (88%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/quiz_view.dart (89%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/assignment_tile.dart (88%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/challenge_button.dart (95%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/challenge_card.dart (94%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/challenge_video.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/complete_button.dart (83%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/example_editor.dart (91%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/help_dialog.dart (98%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/html_card.dart (80%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/parsed_html.dart (93%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/questions.dart (93%) rename {mobile-app-new => mobile-app}/lib/learn/ui/challenge/widgets/quiz_audio_player.dart (95%) rename {mobile-app-new => mobile-app}/lib/learn/ui/daily_challenge/daily_challenge_view.dart (85%) rename {mobile-app-new => mobile-app}/lib/learn/ui/daily_challenge/daily_challenge_viewmodel.dart (81%) rename {mobile-app-new => mobile-app}/lib/learn/ui/daily_challenge/daily_challenge_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_view.dart (93%) rename {mobile-app-new => mobile-app}/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.dart (76%) rename {mobile-app-new => mobile-app}/lib/learn/ui/daily_challenge_archive/daily_challenge_archive_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/editor_challenge/editor_body.dart (94%) rename {mobile-app-new => mobile-app}/lib/learn/ui/editor_challenge/editor_challenge_view.dart (96%) rename {mobile-app-new => mobile-app}/lib/learn/ui/editor_challenge/editor_challenge_viewmodel.dart (94%) rename {mobile-app-new => mobile-app}/lib/learn/ui/editor_challenge/editor_challenge_viewmodel.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/editor_challenge/editor_challenge_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/editor_challenge/editor_panels.dart (90%) rename {mobile-app-new => mobile-app}/lib/learn/ui/landing/daily_challenge_card.dart (96%) rename {mobile-app-new => mobile-app}/lib/learn/ui/landing/landing_view.dart (87%) rename {mobile-app-new => mobile-app}/lib/learn/ui/landing/landing_viewmodel.dart (62%) rename {mobile-app-new => mobile-app}/lib/learn/ui/landing/landing_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/learn/ui/landing/superblock_button.dart (97%) rename {mobile-app-new => mobile-app}/lib/learn/ui/module/module_view.dart (84%) rename {mobile-app-new => mobile-app}/lib/learn/ui/scene_fullscreen/scene_fullscreen_view.dart (88%) rename {mobile-app-new => mobile-app}/lib/learn/ui/superblock/superblock_view.dart (94%) rename {mobile-app-new => mobile-app}/lib/learn/ui/widgets/block_card.dart (97%) rename {mobile-app-new => mobile-app}/lib/learn/ui/widgets/scene/scene_canvas.dart (95%) rename {mobile-app-new => mobile-app}/lib/learn/ui/widgets/scene/scene_controls.dart (93%) rename {mobile-app-new => mobile-app}/lib/learn/ui/widgets/scene/scene_view.dart (79%) delete mode 100644 mobile-app/lib/models/code-radio/code_radio_model.dart delete mode 100644 mobile-app/lib/models/learn/challenge_model.dart delete mode 100644 mobile-app/lib/models/learn/completed_challenge_model.dart delete mode 100644 mobile-app/lib/models/learn/curriculum_model.dart delete mode 100644 mobile-app/lib/models/learn/daily_challenge_model.dart delete mode 100644 mobile-app/lib/models/learn/motivational_quote_model.dart delete mode 100644 mobile-app/lib/models/learn/saved_challenge_model.dart delete mode 100644 mobile-app/lib/models/learn/scene_assets_model.dart delete mode 100644 mobile-app/lib/models/main/portfolio_model.dart delete mode 100644 mobile-app/lib/models/main/profile_ui_model.dart delete mode 100644 mobile-app/lib/models/main/user_model.dart delete mode 100644 mobile-app/lib/models/news/bookmarked_tutorial_model.dart delete mode 100644 mobile-app/lib/models/news/tutorial_model.dart delete mode 100644 mobile-app/lib/models/podcasts/episodes_model.dart delete mode 100644 mobile-app/lib/models/podcasts/podcasts_model.dart rename {mobile-app-new => mobile-app}/lib/news/constants.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/controllers/bookmarks_controller.dart (81%) rename {mobile-app-new => mobile-app}/lib/news/controllers/bookmarks_controller.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/controllers/feed_controller.dart (95%) rename {mobile-app-new => mobile-app}/lib/news/controllers/feed_controller.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/controllers/feed_controller.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/author_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/author_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/author_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/bookmarked_post_model.dart (91%) rename {mobile-app-new => mobile-app}/lib/news/models/bookmarked_post_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/bookmarked_post_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/post_model.dart (95%) rename {mobile-app-new => mobile-app}/lib/news/models/post_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/post_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/post_summary_model.dart (82%) rename {mobile-app-new => mobile-app}/lib/news/models/post_summary_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/post_summary_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/search_post_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/search_post_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/models/search_post_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/repositories/api_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/repositories/api_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/repositories/bookmark_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/repositories/bookmark_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/repositories/search_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/repositories/search_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/services/api_service.dart (85%) rename {mobile-app-new => mobile-app}/lib/news/services/api_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/services/bookmark_service.dart (92%) rename {mobile-app-new => mobile-app}/lib/news/services/bookmark_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/services/search_service.dart (81%) rename {mobile-app-new => mobile-app}/lib/news/services/search_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/ui/author_feed/author_feed_view.dart (89%) rename {mobile-app-new => mobile-app}/lib/news/ui/author_feed/author_feed_viewmodel.dart (71%) rename {mobile-app-new => mobile-app}/lib/news/ui/author_feed/author_feed_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/ui/bookmark_feed/bookmark_feed_view.dart (88%) rename {mobile-app-new => mobile-app}/lib/news/ui/bookmark_post/bookmark_post_view.dart (88%) rename {mobile-app-new => mobile-app}/lib/news/ui/feed/feed_view.dart (63%) rename {mobile-app-new => mobile-app}/lib/news/ui/image/image_view.dart (95%) rename {mobile-app-new => mobile-app}/lib/news/ui/news_shell.dart (92%) rename {mobile-app-new => mobile-app}/lib/news/ui/post/post_view.dart (91%) rename {mobile-app-new => mobile-app}/lib/news/ui/post/post_viewmodel.dart (63%) rename {mobile-app-new => mobile-app}/lib/news/ui/post/post_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/ui/search/search_view.dart (91%) rename {mobile-app-new => mobile-app}/lib/news/ui/search/search_viewmodel.dart (89%) rename {mobile-app-new => mobile-app}/lib/news/ui/search/search_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/news/ui/tag_feed/tag_feed_view.dart (72%) rename {mobile-app-new => mobile-app}/lib/news/ui/widgets/back_to_top_button.dart (97%) rename {mobile-app-new => mobile-app}/lib/news/ui/widgets/bookmark_button.dart (73%) rename {mobile-app-new => mobile-app}/lib/news/ui/widgets/bottom_button.dart (95%) rename {mobile-app-new => mobile-app}/lib/news/ui/widgets/post_feed_list.dart (93%) rename {mobile-app-new => mobile-app}/lib/news/ui/widgets/post_tile.dart (94%) rename {mobile-app-new => mobile-app}/lib/news/ui/widgets/search_result_tile.dart (94%) rename {mobile-app-new => mobile-app}/lib/news/ui/widgets/tag_button.dart (94%) rename {mobile-app-new => mobile-app}/lib/podcasts/constants.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/downloads_controller.dart (89%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/downloads_controller.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/downloads_controller.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/library_controller.dart (85%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/library_controller.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/player_controller.dart (93%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/player_controller.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/controllers/player_controller.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/models/episode_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/models/episode_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/models/episode_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/models/podcast_model.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/models/podcast_model.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/models/podcast_model.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/api_repository.dart (96%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/api_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/download_repository.dart (98%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/download_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/progress_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/progress_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/storage_repository.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/repositories/storage_repository.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/services/api_service.dart (86%) rename {mobile-app-new => mobile-app}/lib/podcasts/services/api_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/services/library_service.dart (91%) rename {mobile-app-new => mobile-app}/lib/podcasts/services/library_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/services/playback_service.dart (92%) rename {mobile-app-new => mobile-app}/lib/podcasts/services/playback_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/downloads/downloads_view.dart (86%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/episode/episode_view.dart (95%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/episode_list/episode_list_view.dart (94%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/episode_list/episode_list_viewmodel.dart (92%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/episode_list/episode_list_viewmodel.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/episode_list/episode_list_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/podcast_list/podcast_list_view.dart (80%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.dart (61%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/podcast_list/podcast_list_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/podcasts_shell.dart (92%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/widgets/download_button.dart (90%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/widgets/episode_tile.dart (92%) rename {mobile-app-new => mobile-app}/lib/podcasts/ui/widgets/podcast_grid_tile.dart (94%) rename {mobile-app-new => mobile-app}/lib/routing/auth.dart (82%) rename {mobile-app-new => mobile-app}/lib/routing/auth.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/routing/code_radio.dart (84%) rename {mobile-app-new => mobile-app}/lib/routing/code_radio.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/routing/learn.dart (84%) rename {mobile-app-new => mobile-app}/lib/routing/learn.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/routing/news.dart (82%) rename {mobile-app-new => mobile-app}/lib/routing/news.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/routing/podcasts.dart (81%) rename {mobile-app-new => mobile-app}/lib/routing/podcasts.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/routing/router.dart (69%) rename {mobile-app-new => mobile-app}/lib/routing/settings.dart (82%) rename {mobile-app-new => mobile-app}/lib/routing/settings.g.dart (100%) delete mode 100644 mobile-app/lib/service/audio/audio_service.dart delete mode 100644 mobile-app/lib/service/authentication/authentication_service.dart delete mode 100644 mobile-app/lib/service/developer_service.dart delete mode 100644 mobile-app/lib/service/dio_service.dart delete mode 100644 mobile-app/lib/service/firebase/analytics_observer.dart delete mode 100644 mobile-app/lib/service/firebase/analytics_service.dart delete mode 100644 mobile-app/lib/service/firebase/remote_config_service.dart delete mode 100644 mobile-app/lib/service/learn/daily_challenge_notification_service.dart delete mode 100644 mobile-app/lib/service/learn/daily_challenge_service.dart delete mode 100644 mobile-app/lib/service/learn/learn_file_service.dart delete mode 100644 mobile-app/lib/service/learn/learn_offline_service.dart delete mode 100644 mobile-app/lib/service/learn/learn_service.dart delete mode 100644 mobile-app/lib/service/learn/scene_assets_service.dart delete mode 100644 mobile-app/lib/service/locale_service.dart delete mode 100644 mobile-app/lib/service/navigation/quick_actions_service.dart delete mode 100644 mobile-app/lib/service/news/api_service.dart delete mode 100644 mobile-app/lib/service/news/bookmark_service.dart delete mode 100644 mobile-app/lib/service/news/legacy/bookmark_sqlite_migrator.dart delete mode 100644 mobile-app/lib/service/podcast/download_service.dart delete mode 100644 mobile-app/lib/service/podcast/legacy/podcasts_sqlite_migrator.dart delete mode 100644 mobile-app/lib/service/podcast/notification_service.dart delete mode 100644 mobile-app/lib/service/podcast/podcasts_service.dart delete mode 100644 mobile-app/lib/service/storage/json_file_store.dart rename {mobile-app-new => mobile-app}/lib/services/analytics_service.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/analytics_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/audio_service.dart (97%) rename {mobile-app-new => mobile-app}/lib/services/audio_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/auth_service.dart (89%) rename {mobile-app-new => mobile-app}/lib/services/auth_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/dio_service.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/dio_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/notification_service.dart (98%) rename {mobile-app-new => mobile-app}/lib/services/notification_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/remote_config_service.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/remote_config_service.freezed.dart (100%) rename {mobile-app-new => mobile-app}/lib/services/remote_config_service.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/settings/ui/settings/delete_account/delete_account_view.dart (92%) rename {mobile-app-new => mobile-app}/lib/settings/ui/settings/delete_account/delete_account_viewmodel.dart (83%) rename {mobile-app-new => mobile-app}/lib/settings/ui/settings/delete_account/delete_account_viewmodel.g.dart (100%) rename {mobile-app-new => mobile-app}/lib/settings/ui/settings/settings_view.dart (85%) delete mode 100644 mobile-app/lib/ui/theme/fcc_theme.dart delete mode 100644 mobile-app/lib/ui/views/code_radio/code_radio_view.dart delete mode 100644 mobile-app/lib/ui/views/code_radio/code_radio_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/block_template_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/block_template_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/dialogue/dialogue_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/dialogue/dialogue_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/grid/grid_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/grid/grid_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/link/link_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/link/link_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/list/list_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/block/templates/list/list_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/challenge_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/challenge_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/english/english_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/english/english_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/multiple_choice/multiple_choice_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/multiple_choice/multiple_choice_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/python-project/python_project_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/python-project/python_project_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/python/python_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/python/python_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/quiz/quiz_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/quiz/quiz_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/review/review_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/review/review_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/template_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/challenge/templates/template_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/chapter/chapter_block_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/chapter/chapter_block_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/chapter/chapter_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/chapter/chapter_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/landing/landing_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/superblock/superblock_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/superblock/superblock_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/test_runner.dart delete mode 100644 mobile-app/lib/ui/views/learn/utils/challenge_utils.dart delete mode 100644 mobile-app/lib/ui/views/learn/utils/learn_globals.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/assignment_widget.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/audio/audio_player_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/audio/audio_player_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/challenge_card.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/challenge_tile.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/challenge_widgets/project_preview.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/challenge_widgets/symbol_bar.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/console/console_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/console/console_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/custom_alert_widget.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/daily_challenge_card.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/description/description_widget_model.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/description/description_widget_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/example_editor.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/explanation_widget.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/hint/hint_widget_model.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/hint/hint_widget_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/pass/pass_widget_model.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/pass/pass_widget_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/quiz_audio_player.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/quiz_widget.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/scene/scene_view.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/scene/scene_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/transcript_widget.dart delete mode 100644 mobile-app/lib/ui/views/learn/widgets/youtube_player_widget.dart delete mode 100644 mobile-app/lib/ui/views/login/native_login_view.dart delete mode 100644 mobile-app/lib/ui/views/login/native_login_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/html_handler/html_handler.dart delete mode 100644 mobile-app/lib/ui/views/news/news-author/news_author_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-author/news_author_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_feed_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/news-bookmark/news_bookmark_widget.dart delete mode 100644 mobile-app/lib/ui/views/news/news-feed/news_feed_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-feed/news_feed_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/news-image-viewer/news_image_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-image-viewer/news_image_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/news-search/news_search_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-search/news_search_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-tutorial/news_tutorial_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/news-view-handler/news_view_handler_view.dart delete mode 100644 mobile-app/lib/ui/views/news/news-view-handler/news_view_handler_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/news/widgets/back_to_top_button.dart delete mode 100644 mobile-app/lib/ui/views/news/widgets/tag_widget.dart delete mode 100644 mobile-app/lib/ui/views/news/widgets/tutorial_list_widget.dart delete mode 100644 mobile-app/lib/ui/views/podcast/episode-list/episode_list_view.dart delete mode 100644 mobile-app/lib/ui/views/podcast/episode-list/episode_list_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/podcast/episode/episode_view.dart delete mode 100644 mobile-app/lib/ui/views/podcast/episode/episode_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/podcast/podcast-list/podcast_list_view.dart delete mode 100644 mobile-app/lib/ui/views/podcast/podcast-list/podcast_list_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/podcast/widgets/podcast_title_widget.dart delete mode 100644 mobile-app/lib/ui/views/profile/profile_view.dart delete mode 100644 mobile-app/lib/ui/views/profile/profile_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/settings/delete-account/delete_account_view.dart delete mode 100644 mobile-app/lib/ui/views/settings/delete-account/delete_account_viewmodel.dart delete mode 100644 mobile-app/lib/ui/views/settings/settings_view.dart delete mode 100644 mobile-app/lib/ui/views/settings/settings_viewmodel.dart delete mode 100644 mobile-app/lib/ui/widgets/drawer_widget/drawer_tile.dart delete mode 100644 mobile-app/lib/ui/widgets/drawer_widget/drawer_web_buttton.dart delete mode 100644 mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_view.dart delete mode 100644 mobile-app/lib/ui/widgets/drawer_widget/drawer_widget_viewmodel.dart delete mode 100644 mobile-app/lib/ui/widgets/setup_dialog_ui.dart rename {mobile-app-new => mobile-app}/lib/utils/custom_tab.dart (100%) rename {mobile-app-new => mobile-app}/lib/utils/date_utils.dart (100%) delete mode 100644 mobile-app/lib/utils/helpers.dart rename {mobile-app-new => mobile-app}/lib/utils/riverpod.dart (100%) delete mode 100644 mobile-app/lib/utils/upgrade_controller.dart rename {mobile-app-new => mobile-app}/lib/widgets/dialog/confirm_dialog.dart (98%) rename {mobile-app-new => mobile-app}/lib/widgets/drawer/drawer.dart (89%) rename {mobile-app-new => mobile-app}/lib/widgets/drawer/drawer_tile.dart (100%) rename {mobile-app-new => mobile-app}/lib/widgets/drawer/scroll_shadow.dart (100%) rename {mobile-app-new => mobile-app}/lib/widgets/error_retry.dart (100%) rename {mobile-app-new => mobile-app}/lib/widgets/html_handler/html_handler.dart (98%) rename {mobile-app-new => mobile-app}/lib/widgets/section_divider.dart (100%) diff --git a/.github/workflows/flutter-checks.yml b/.github/workflows/flutter-checks.yml index 090c09c05..9722b8d3e 100644 --- a/.github/workflows/flutter-checks.yml +++ b/.github/workflows/flutter-checks.yml @@ -46,6 +46,11 @@ jobs: - name: Get dependencies run: flutter pub get + - name: Analyze + run: | + dart analyze --no-fatal-warnings + flutter analyze --no-fatal-warnings + - name: Check generated files are up to date run: | dart run build_runner build @@ -55,7 +60,3 @@ jobs: echo "::error::Generated files are out of date. Run 'dart run build_runner build' and commit the result." exit 1 fi - - # dart analyze, not flutter analyze: only dart analyze runs analyzer plugins such as riverpod_lint - - name: Analyze - run: dart analyze --no-fatal-warnings diff --git a/.github/workflows/flutter-deploy.yml b/.github/workflows/flutter-deploy.yml index 29eaeb904..8add3ecfd 100644 --- a/.github/workflows/flutter-deploy.yml +++ b/.github/workflows/flutter-deploy.yml @@ -92,6 +92,21 @@ jobs: - name: Install packages run: flutter pub get + - name: Analyze + run: | + dart analyze --no-fatal-warnings + flutter analyze --no-fatal-warnings + + - name: Check generated files are up to date + run: | + dart run build_runner build + if [ -n "$(git status --porcelain -- .)" ]; then + git status --porcelain -- . + git diff -- . + echo "::error::Generated files are out of date. Run 'dart run build_runner build' and commit the result." + exit 1 + fi + - name: Run tests in emulator uses: reactivecircus/android-emulator-runner@1dcd0090116d15e7c562f8db72807de5e036a4ed # v2 with: diff --git a/.lintstagedrc.json b/.lintstagedrc.json index 6514a572e..33b753f72 100644 --- a/.lintstagedrc.json +++ b/.lintstagedrc.json @@ -1,3 +1,6 @@ { - "mobile-app/**/*.dart": "bash -c 'cd mobile-app && dart analyze --no-fatal-warnings'" + "mobile-app/**/*.dart": [ + "dart analyze --no-fatal-warnings", + "flutter analyze --no-fatal-warnings" + ] } diff --git a/AGENTS.md b/AGENTS.md index 856abb35a..6f69dbeb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,26 +4,24 @@ freeCodeCamp's mobile app monorepo. | Folder | What it is | |---|---| -| `mobile-app/` | v7 (Stacked MVVM), package `freecodecamp`. Still receives bug fixes. | -| `mobile-app-new/` | v8 (Riverpod 3 + GoRouter + Freezed), package `mobile_app_new`. | +| `mobile-app/` | Flutter app (Riverpod 3 + GoRouter + Freezed), package `freecodecamp`. | | `mobile-api/` | Node backend for podcasts (`/podcasts`, `/podcasts/{id}/episodes`). | -v8 is the next version of the **same app**, not a separate product. When the migration is finished, `mobile-app-new/lib/` (plus `integration_test/`, `test_driver/` and assets) is copied into `mobile-app/`, and imports are rewritten from `package:mobile_app_new/` to `package:freecodecamp/`. Native `android/` and `ios/` config comes from v7 untouched. v8's `1.0.0+1` version and placeholder native config never ship. - -Everything below describes `mobile-app-new/` unless stated otherwise. The provider dependency graph is in [docs/LAYER_MAP.md](docs/LAYER_MAP.md). +Everything below describes `mobile-app/`. The provider dependency graph is in [docs/LAYER_MAP.md](docs/LAYER_MAP.md). ## Commands -Run from `mobile-app-new/`: +Run from `mobile-app/`: ```sh dart run build_runner build # after touching any @riverpod, @freezed, @JsonSerializable or @TypedGoRoute -dart analyze # NOT flutter analyze, and NOT from a subdirectory +dart analyze +flutter analyze flutter drive --driver=test_driver/integration_test.dart --target=integration_test//_test.dart dart integration_test_runner.dart [--curriculum] [--ios] ``` -- Use `dart analyze` from the project root. `flutter analyze` and runs from inside `lib/` silently skip the `riverpod_lint` plugin and report "No issues found!". +- Run both analyzers from the project root. - A clean analyze is not verification. Run the change on a device. - `.env` is bundled as an asset and must exist. Keys: `ALGOLIAAPPID`, `ALGOLIAKEY`, `HASHNODE_PUBLICATION_ID`, `AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, `DEVELOPMENT_MODE` (switches .org/.dev), `SHOW_ALL_SB` (show all superblocks). @@ -82,17 +80,16 @@ Rules: ## Persisted data -v8 reads what v7 wrote to the same documents directory and SharedPreferences, so **paths and keys are a migration contract**. Downgrades never happen, so v8 may write formats v7 can't read. +Installed apps already hold this data in the documents directory and SharedPreferences, so **paths and keys never change**. Downgrades never happen, so a format may change as long as the previous one is still read. | Data | Location | |---|---| -| Bookmarks | `storage/bookmarked-articles.json`, v7 keys `articleId`/`articleTitle`/`authorName`/`articleText` (mapped only in `bookmark_service.dart`) | -| Podcast downloads | `storage/podcast-downloads.json` (version 2, API key names; v7's version 1 is normalised in memory on read), `episodes/`, `images/` | +| Bookmarks | `storage/bookmarked-articles.json`, keys `articleId`/`articleTitle`/`authorName`/`articleText` (mapped only in `bookmark_service.dart`) | +| Podcast downloads | `storage/podcast-downloads.json` (version 2, API key names; version 1 is normalised in memory on read), `episodes/`, `images/` | | Podcast resume | prefs `{episodeId}_progress`, LRU index `podcast_recent_episodes` (cap 10) | | Learn saved code | prefs `..` | - JSON files live inside the feature's own repository: an atomic write (`.tmp` then `rename`, no delete first), a serialised `Future` queue, and tolerant reads. Don't build a generic store. -- Remote Config sets a minimum app version of 7.5.6, so data migrations older than that (SQLite → JSON) are not ported. - Podcast completion is signalled by the store row, never by file existence: dio writes the `.mp3` in place while downloading. The orphan sweep reads raw store entries, not parsed models. ## Audio @@ -112,12 +109,12 @@ v8 reads what v7 wrote to the same documents directory and SharedPreferences, so ## Conventions - No `///` doc comments. Use plain one-line `//` only where a reader would otherwise change the code and break it. -- Strings are hardcoded English. Localisation is not migrated. +- Strings are hardcoded English. There is no localisation. - No unit tests yet. The curriculum e2e (`integration_test/test_runner/curriculum_test.dart`, run via `dart integration_test_runner.dart --curriculum`) is the required test. Other e2e tests live in `integration_test//`: - Use type-based finders (`find.byType`, `tester.widget().field`). Widgets used as finders (e.g. `NewsPostHeader`) stay public. - Device stdout is lossy on long runs. Return results through `binding.reportData`. - Use `ErrorRetry` (`lib/widgets/error_retry.dart`) for async error states. - Always give `CachedNetworkImage` explicit bounds. Unsized, it fills its constraints or jumps while loading. - Cover art (podcasts, Code Radio) is always square: use `AspectRatio(1)` and never crop. -- Pin a dependency to v7's exact version when v7 already uses it; new dependencies take the latest version. +- Pin every dependency to the exact version it was tested with (no `^`). New dependencies start at the latest version. - Hashnode's GraphQL API is slow upstream, and `graphql` 5.2.4 applies a 5s default request timeout. diff --git a/codemagic.yaml b/codemagic.yaml index 5d5028f33..d960b0856 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -43,6 +43,11 @@ workflows: - name: Install dependencies script: flutter pub get + - name: Analyze code + script: | + dart analyze --no-fatal-warnings + flutter analyze --no-fatal-warnings + - name: Check generated files are up to date script: | dart run build_runner build @@ -53,9 +58,6 @@ workflows: exit 1 fi - - name: Analyze code - script: dart analyze --no-fatal-warnings - - name: Install applesimutils script: | brew tap wix/brew @@ -132,6 +134,11 @@ workflows: - name: Install dependencies script: flutter pub get + - name: Analyze code + script: | + dart analyze --no-fatal-warnings + flutter analyze --no-fatal-warnings + - name: Check generated files are up to date script: | dart run build_runner build @@ -142,9 +149,6 @@ workflows: exit 1 fi - - name: Analyze code - script: dart analyze --no-fatal-warnings - - name: Install applesimutils script: | brew tap wix/brew diff --git a/docs/LAYER_MAP.md b/docs/LAYER_MAP.md index bc3f04861..4566b1984 100644 --- a/docs/LAYER_MAP.md +++ b/docs/LAYER_MAP.md @@ -1,6 +1,6 @@ # Layer Map -Every provider dependency in the v8 app (`mobile-app-new/lib/`): which controllers reach which services, which services own which repositories, and where each repository gets its data. Views are not drawn. +Every provider dependency in the app (`mobile-app/lib/`): which controllers reach which services, which services own which repositories, and where each repository gets its data. Views are not drawn. ## Legend @@ -364,7 +364,7 @@ flowchart TD ## Regenerating -The edges come from resolving every `ref.watch` / `ref.read` / `ref.listen` in `lib/` back to the file that declares the provider (map each `final xProvider` in `*.g.dart` to its source file). From `mobile-app-new/`: +The edges come from resolving every `ref.watch` / `ref.read` / `ref.listen` in `lib/` back to the file that declares the provider (map each `final xProvider` in `*.g.dart` to its source file). From `mobile-app/`: ```sh for f in $(find lib -name '*.dart' ! -name '*.g.dart' ! -name '*.freezed.dart' | sort); do diff --git a/mobile-app-new/.gitignore b/mobile-app-new/.gitignore deleted file mode 100644 index 3820a95c6..000000000 --- a/mobile-app-new/.gitignore +++ /dev/null @@ -1,45 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.build/ -.buildlog/ -.history -.svn/ -.swiftpm/ -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-dependencies -.pub-cache/ -.pub/ -/build/ -/coverage/ - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release diff --git a/mobile-app-new/.metadata b/mobile-app-new/.metadata deleted file mode 100644 index 2059ed534..000000000 --- a/mobile-app-new/.metadata +++ /dev/null @@ -1,33 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled and should not be manually edited. - -version: - revision: "e1fd963c6f6922bd32afde2e9698a363cd0406d2" - channel: "stable" - -project_type: app - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 - base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 - - platform: android - create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 - base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 - - platform: ios - create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 - base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/mobile-app-new/README.md b/mobile-app-new/README.md deleted file mode 100644 index 6c4791dd9..000000000 --- a/mobile-app-new/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# mobile_app_new - -A new Flutter project. - -## Getting Started - -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) -- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/mobile-app-new/analysis_options.yaml b/mobile-app-new/analysis_options.yaml deleted file mode 100644 index 92e580f82..000000000 --- a/mobile-app-new/analysis_options.yaml +++ /dev/null @@ -1,33 +0,0 @@ -include: package:flutter_lints/flutter.yaml - -analyzer: - exclude: - # Flutter generated files - - build/** - - android/** - - ios/** - - web/** - - windows/** - - macos/** - - linux/** - language: - strict-raw-types: true - strict-casts: true - errors: - todo: ignore - -linter: - rules: - avoid_print: false - prefer_single_quotes: true - use_build_context_synchronously: false - always_use_package_imports: true - avoid_relative_lib_imports: true - directives_ordering: true - sort_pub_dependencies: true - -plugins: - riverpod_lint: 3.1.4 - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/mobile-app-new/android/.gitignore b/mobile-app-new/android/.gitignore deleted file mode 100644 index be3943c96..000000000 --- a/mobile-app-new/android/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -gradle-wrapper.jar -/.gradle -/captures/ -/gradlew -/gradlew.bat -/local.properties -GeneratedPluginRegistrant.java -.cxx/ - -# Remember to never publicly share your keystore. -# See https://flutter.dev/to/reference-keystore -key.properties -**/*.keystore -**/*.jks diff --git a/mobile-app-new/android/app/build.gradle.kts b/mobile-app-new/android/app/build.gradle.kts deleted file mode 100644 index dab2541ff..000000000 --- a/mobile-app-new/android/app/build.gradle.kts +++ /dev/null @@ -1,58 +0,0 @@ -plugins { - id("com.android.application") - // START: FlutterFire Configuration - id("com.google.gms.google-services") - id("com.google.firebase.crashlytics") - // END: FlutterFire Configuration - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id("dev.flutter.flutter-gradle-plugin") -} - -android { - namespace = "org.freecodecamp.mobile_app_new" - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion - - compileOptions { - isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "org.freecodecamp" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = flutter.minSdkVersion - targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName - manifestPlaceholders["auth0Domain"] = "freecodecamp-dev.auth0.com" - manifestPlaceholders["auth0Scheme"] = "org.freecodecamp" - } - - buildTypes { - release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig = signingConfigs.getByName("debug") - manifestPlaceholders["auth0Domain"] = "auth.freecodecamp.org" - manifestPlaceholders["auth0Scheme"] = "https" - } - } -} - -dependencies { - coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") -} - -kotlin { - compilerOptions { - jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 - } -} - -flutter { - source = "../.." -} diff --git a/mobile-app-new/android/app/google-services.json b/mobile-app-new/android/app/google-services.json deleted file mode 100644 index 0d7ca0666..000000000 --- a/mobile-app-new/android/app/google-services.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "project_info": { - "project_number": "363152234407", - "project_id": "mobile-4ee8a", - "storage_bucket": "mobile-4ee8a.firebasestorage.app" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:363152234407:android:6293f9873ae6df8a738882", - "android_client_info": { - "package_name": "org.freecodecamp" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyCP3yAWau0JJE8BPgsUrbT2K27EJX3z2KE" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/mobile-app-new/android/app/src/debug/AndroidManifest.xml b/mobile-app-new/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 399f6981d..000000000 --- a/mobile-app-new/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/mobile-app-new/android/app/src/main/AndroidManifest.xml b/mobile-app-new/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 3a6ba0ed2..000000000 --- a/mobile-app-new/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt b/mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt deleted file mode 100644 index dd81e9691..000000000 --- a/mobile-app-new/android/app/src/main/kotlin/org/freecodecamp/mobile_app_new/MainActivity.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.freecodecamp.mobile_app_new - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() diff --git a/mobile-app-new/android/app/src/main/res/drawable-hdpi/notification_icon.png b/mobile-app-new/android/app/src/main/res/drawable-hdpi/notification_icon.png deleted file mode 100644 index 337291b9d46e0b24aabbb991eca579e85ef3fa6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 759 zcmV2(gre?vgR+FEFCJQT)gt3sHs3}qsp)_l=&}e2Qvzm>jW+r9C!onD#G$9-L zD=d&~EaZQ|Mqvyd_cPCP#yR)A=Vcnp_o_S1x$g6v@B7SmUTU?9ii!$DMCKtUkj?C- zgU<80^Vwzb7xNeyL)Nip&{@E1InZ5;d`F(>IqWBa(oEzU@)Oz3z6kVski)#51KnLd z|GJ*XJ^_UzVsx_0f$p&HImj*tx*fi!gWUv>O~@DIE;56C5n`X{2Os_oS;n0rbUt$1 z&ud~o4n&_1Ud=8Cx-GsZFv6W2=&tfTee6^=AfJ)@?8-p@e0U|!FJv+Ia-e%p?A!(x zn|xqOf7#0uZj#p0BU#zbU5Z@c#c z55)eul&M?+b9~Priy>qzSp^?TFRa+de+C&ro_p_D z+=+pkg}f6xw~oCQj{1=F#lW{A1H2y4`N%8elb*|d0?0%9lEFDC`WS2DorTC*UJvL#q+j@f>A2VVN*ntG zFJJWXarObYC6f7fvDmF$=oFpn^Yxs=2#jb06tB?6aWUDE7aD p7IxDC^Va*^e|m^lR8;(Lego0u6|Q5Emc#%6002ovPDHLkV1mnheO~|o diff --git a/mobile-app-new/android/app/src/main/res/drawable-mdpi/notification_icon.png b/mobile-app-new/android/app/src/main/res/drawable-mdpi/notification_icon.png deleted file mode 100644 index e8c45718dae417fda7a3dcd78099751acec69808..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 479 zcmV<50U-W~P)A z$mSyaG03;R2tN~6VN<$nE(61G0%4m4BD;XSUv3}^j$yV;7_gC&@<^CJ8fIMT)zbgP-bm%G39 z<-2}8>DQuX$V#xU3%0c(2RN-}0>9iBy+O72=DZiED#5;KnAJubTsd=atjm1Krt%u$ z8XjHjwJkP#L)d#Gx VAPSi1GmQWM002ovPDHLkV1g<_)9L^K diff --git a/mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml deleted file mode 100644 index f74085f3f..000000000 --- a/mobile-app-new/android/app/src/main/res/drawable-v21/launch_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/mobile-app-new/android/app/src/main/res/drawable-xhdpi/notification_icon.png b/mobile-app-new/android/app/src/main/res/drawable-xhdpi/notification_icon.png deleted file mode 100644 index 7ff7f93368aa2d7a9b4ef5f65a3d48337ea236d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1080 zcmV-81jqY{P)zi47?X|z6qKu4;jEs!`84)Rm z)^S1=OGY z5KyjyozKD*hE1;L5X4=b9@ekhV(0?GrZCuc%H-Ar-wQB7k17ZB(m z`GJqJ9|~$Kq3_lwVh`s+LG5Adp>ylNeh!$^E%*(pWl26WNXctB1#u^WXmc6Qz6R>dY-KMLagYBrzCa~h7Yb_c zTc0}i)Ld=mdG)2Csvzdq4D}=Zk_o~(VoPesm z-!EBylKjEL4K)X?&o=hd+-~Lp_6mWTRaWcs2^u2q*`ha|vK8tGH9^<)XYQ2-^82mN z4))Y+H1iO9X&^rk`e?O2^Gw=S+{sv}Wlv4L zu@qgqzdtBqKyIwn_-v=la1IbN{CpC~brm-^WitEGUn)9pOkh6_lZjZlbc|8ag z{Ll(=YpmupdvPGQ!1B6$(vRc_9JTNrs0;fHGRrLA!d?={F1N{yVUH2F)7`c);*Zik z;{M&y-M*fEM%@hl~jjiZh3Vz^UKtYA% z@BCNLaLzE|)@w0ej}Z5TRiYn(%m^D((K%FbCJ1p4q3HegIQ#7d#yVzW>ULhmew#pp y-s0Z0CbHLFV5}N(H}Btlxto!Zk&%({uk#aK&ztK7eT(h@0000oW^Ulow?U~u1{Lp@25&Y|2@7nV}tLNXN{%j(|qzBfb{L&xpfV(^J6Mh9+ZF z_98A|O;FIF3-MRdP=XI)!Rw9%OSApvj779CvTU&M>jC=uq z>=6ohZ!>*f=X?fn_n&loKv~I}5Qtm&RzaUK?e7mYknF@!*PG>ikQ`&gO7Q!SxILOr zs82gO`2)?QUSn9(0?9K-9ArDg0?&J?2eX6!0KSn9Oc)jT9!I`%6!(eK=Lvp+|T(Yz;6iRedAxmm5Fu> z{ry4XvmUYHfOB)it@~20;Cu^B8sU$eZ-Qi}_C>sj=#ywyQ|`YNx46qeA0oQp73}ht z>>0b5^DV^f{-^BYd|lvmx@1pdM)#Sf7=0Jz_0k+H*;V*<+oK8a`oZ`v;(QAvFEm13 zr!>CS)kCs#t0Fd0&E=GTSMpCjon%h1b#)LoKD#Enh&3&coN9zRfT66Z3Ovp+o<)3! zIFxFZvZ#cIY+W71JrwMW{%qE?K=MQ*R8C+`Rp9Xyd0OXhQiVj0W0z6z*cWxV!sJ_kh4n}V&JuL9~<7%%1~)~6u%8jHK(HmYp@T zJ2;<1vb%|THr0NTq4o~rWzqNKnH#eOxu?0H8*0BI688kNSIph4$szeoq)DvtMapls zYgubS{yHw`hHJRq7jb)1mh)JXL)?VyCsWjZ6R8B$US)hzbW%8)wHD+DA$Cz+8PmAl z7u0SrK6=c9SyPB)*Ft(Z3}THBsGDefQeI(Q5zq}pT#MK)?)8sO7q%K7Mb}WZ7S!Sv zsb3neEvzX8;yI>JsrB{|b$!IG&@H#K&KGg_44qY{JkA6mc*1>^6l?;QF)92h=^5c+|ecPBT*jbU)`F&iNqj?9pwLF3$O)V>id4JiwaDh?^I^O~yB~&IfVZu8&B&1z`cGStbp> zW7IL*0}yxn@SFFv14d&d`}vxln%A?=2XXhd3Ho2ol>zN2#F_keUr>8PGUgOLmRk4I zQp9Zw*qt|RxRiCiNQIBFq~;VuLGAKntSNd7<(pznfbjy!e(a<(*&(d!DTrIL*f$nB z_Dfh76mg5$bOo$a^;%Q?gMqjSVfrbLE}0r_0D6N)M~K^45BBP%Q;7SJ{8Bks|5hUs li9{liNF)-8L?V$+^Dh$1qlZ4Jz9|3z002ovPDHLkV1o2pIm`e6 diff --git a/mobile-app-new/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png b/mobile-app-new/android/app/src/main/res/drawable-xxxhdpi/notification_icon.png deleted file mode 100644 index e9db5cbd42c6111d613058fc7d4d35d9a9aeb45d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2300 zcmVa~f&3hhpN3(ouYL8xY?!#1r; z`ygxM+q6AZGdirqJhTgF|II3vbEIl#I&9Ms(njN#w0TuK3hr!>@JA`=YoIw%HL^QsXjD}aSA5z&11|hXrE4Lv2!_XF4aea!A{Z|kk4t` zt2PFV^EB-NOyGsjm|MfYXv!(7jRFJhK>Lpbx0}DCYGc7z&t!3*pe>=-W5bx+(LT!? zpJDf8XVnIU0al}ZD1rY?+e5W6VZ7fL;{OVh`4D!=+Fs;i+FGg&0v#^^Z>(~HYGVQ) zi~mdN@TC1;wXtE$4Pzc7^(b<>>SM#0i_m^6^{^*&W7YP8j^?I4Dgo?;v$bl+ zfN}4Wx)iZK`8!%BHjKGt7Q2Yuv$<8<8F-pOxk`0oz_^Q{Jo->xqb;OmW5bx2YDLKI zs_O(i!S<>+{VQz+)s6wFSA{g=%u<#?|>~EM*o`-SQMtK4_9Ve-e*ub9xyGKPHQC%zG4c08@<*FMC z#=c1E(!?7Wd01*N=4Db3yN7G5t`Sr}PMfK24B+3asw7wwTd1$tFy>}j338h18eyU5 z#fgnBh7YTHz_@2hUD`5_jyxfK2ILvBbhK9F&*(Dx2ngezCUyA- z+KeJ8T38Aiho~+ljCX}rpa#o8u0?xHFJwHWzQ=;Gzps@cOR25|MJs4249RD7gt4|q zc?Mp`HCdUX^g_mL@x1|U>Ei!l!8kihU5r&#R|3B7t9(y&F@W#Ac~67aV2||5SvkA0 zRUA|BZ7=nwXm{A(Uqy`reD{R1uIfs_b3n>As*3^RT&IDj;b(<%^P+mI$d(07F|)gW z{{L85>_WVpRv&=%sY>)!Cir%jwq1|yL!GiG2%dZ^VoRtCXV;dby;HbmyZ06=mJbd6 zZ&iqw)4XGXqLsB2BF}>9e?i)t8gv>;dyr5b4cQRXc2KMe-#?^uPmpzh{=cgMk#(8V z0{^xsJE<-t^nbhtoQ4~8m?yJ&d>Ugy7v zz73D7uTaqU2@=bmxOSs23SW`Jm#QWdC~tYG!?uA}HUQq&Yx=wm_M|VT{z3phH&Nsj z)zm`qy;Ar=)r5e4mq&SA+#+x9nYN}1OWkTPAk zQvD4DK9twd*Ac3#h2l-5uo3)F8hy^zAQkMZ4=?6Nc5@z9T_Y@XqBt=&SKSa$y`eS7_5rn?sZ#r6bO4^JZ7}ek8qlHPC8`?&{T(j;jUTD5HSiTGU7&xLwxDYJLSMfS|Hccd3jzFC zRgQI48>pr)@I#FH^0igFWmVe=lxM}p@v7?!eXWl2CZ^zHa|2UPdETPehd_TnEcni8 zKl`nNPF8_`l0|HUvZ8AH0)KfH3Hq|n!B^d#wP$s`-WPaIOHaqxJSt4~0OhTU#_g)> z3waGWNWj005L7x)rho*BJfuMY+O10Tkn z2soj%dyx4pVjIGOjTYxXhr6LXimKpUT}j(f@dq6cYL*kzRrP zwUHT@An=FX8VgK23+3+UDAmq%*rr3JjmEi{)Xgsf-`!xtJj0d|gAGgnUx3fnZ60A* zTSxtwwnpHoWn08)-hMYjfp3Ff69Lx{5{X12kw_#Gi9{liNF)-8L?V$$Boc|QH~#}` WBP#H`*b7tu0000 - - - - - - - diff --git a/mobile-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index db77bb4b7b0906d62b1847e87f15cdcacf6a4f29..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/mobile-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ diff --git a/mobile-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/mobile-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile-app-new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 4d6372eebdb28e45604e46eeda8dd24651419bc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` diff --git a/mobile-app-new/android/app/src/main/res/values-night/styles.xml b/mobile-app-new/android/app/src/main/res/values-night/styles.xml deleted file mode 100644 index 06952be74..000000000 --- a/mobile-app-new/android/app/src/main/res/values-night/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/mobile-app-new/android/app/src/main/res/values/styles.xml b/mobile-app-new/android/app/src/main/res/values/styles.xml deleted file mode 100644 index cb1ef8805..000000000 --- a/mobile-app-new/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - diff --git a/mobile-app-new/android/app/src/main/res/xml/network_security_config.xml b/mobile-app-new/android/app/src/main/res/xml/network_security_config.xml deleted file mode 100644 index de61259aa..000000000 --- a/mobile-app-new/android/app/src/main/res/xml/network_security_config.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - localhost - - diff --git a/mobile-app-new/android/app/src/profile/AndroidManifest.xml b/mobile-app-new/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f6981d..000000000 --- a/mobile-app-new/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/mobile-app-new/android/build.gradle.kts b/mobile-app-new/android/build.gradle.kts deleted file mode 100644 index dbee657bb..000000000 --- a/mobile-app-new/android/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -val newBuildDir: Directory = - rootProject.layout.buildDirectory - .dir("../../build") - .get() -rootProject.layout.buildDirectory.value(newBuildDir) - -subprojects { - val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) - project.layout.buildDirectory.value(newSubprojectBuildDir) -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean") { - delete(rootProject.layout.buildDirectory) -} diff --git a/mobile-app-new/android/gradle.properties b/mobile-app-new/android/gradle.properties deleted file mode 100644 index e96108cfe..000000000 --- a/mobile-app-new/android/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -# This newDsl flag was added by the Flutter template -android.newDsl=false -# This builtInKotlin flag was added by the Flutter template -android.builtInKotlin=false diff --git a/mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties b/mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 2d428bfb1..000000000 --- a/mobile-app-new/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/mobile-app-new/android/settings.gradle.kts b/mobile-app-new/android/settings.gradle.kts deleted file mode 100644 index 00f226cb7..000000000 --- a/mobile-app-new/android/settings.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -pluginManagement { - val flutterSdkPath = - run { - val properties = java.util.Properties() - file("local.properties").inputStream().use { properties.load(it) } - val flutterSdkPath = properties.getProperty("flutter.sdk") - require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } - flutterSdkPath - } - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "9.0.1" apply false - // START: FlutterFire Configuration - id("com.google.gms.google-services") version("4.4.4") apply false - id("com.google.firebase.crashlytics") version("3.0.7") apply false - // END: FlutterFire Configuration - id("org.jetbrains.kotlin.android") version "2.3.20" apply false -} - -include(":app") diff --git a/mobile-app-new/assets/fonts/Hack-Regular.ttf b/mobile-app-new/assets/fonts/Hack-Regular.ttf deleted file mode 100644 index 75a253b4740b671a2d473b44505e9e4a82b13715..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 305172 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