diff --git a/.firebaserc b/.firebaserc index e08602a..0500998 100644 --- a/.firebaserc +++ b/.firebaserc @@ -1,5 +1,5 @@ -{ - "projects": { - "default": "myedu-d90cd" - } -} +{ + "projects": { + "default": "myedu-d90cd" + } +} diff --git a/.gitignore b/.gitignore index 0495a98..a473296 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,49 @@ -# 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 -.flutter-plugins-dependencies -.pub-cache/ -.pub/ -/build/ - -# 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 - - -lib/firebase_options.dart -firebase.json +# 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 +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# 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 + + +lib/firebase_options.dart +firebase.json diff --git a/.metadata b/.metadata index e8f7bf9..5f4336f 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "ea121f8859e4b13e47a8f845e4586164519588bc" + revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: android - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: ios - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: linux - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: macos - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: web - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: windows - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 # User provided section diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4184e2a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cmake.sourceDirectory": "/home/azrul/Github/my_edu/linux" +} \ No newline at end of file diff --git a/README.md b/README.md index 25e755c..ac52fde 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,114 @@ -# MyEdu - University Suite ๐ŸŽ“๐Ÿ“ฑ - -**MyEdu - University Suite** is a comprehensive university management app designed to streamline academic and administrative processes for students and institutions alike. Built with **Flutter**, this all-in-one solution brings everything a student needs, right at their fingertips. - -CleanShot 2025-08-08 at 11 52 10@2x - - - ---- - -## โœจ Features - -- **๐Ÿ“Š Dashboard** - A clean and modern dashboard overviewing important academic information. - -- **๐Ÿ“š Courses** - Browse all enrolled and available courses with full details. - -- **๐Ÿ“† Class Routines** - View weekly class schedules and plan your day accordingly. - -- **๐Ÿ“ Academic Result** - Access detailed results with **full marksheet export in PDF**. - -- **๐Ÿ’ณ Academic Fees** - View due fees and pay online via **SSLCommerz Payment Gateway**. - -- **๐Ÿงพ Payment Receipt & Notification** - Automatic **receipt generation**, plus **email and SMS** notifications after payment. - -- **๐Ÿ“ข Notice Board** - Get real-time university notices and announcements. - -- **๐Ÿ“ฒ Virtual ID Card** - NFC-powered digital ID card for easy campus access and identification. - ---- - -## ๐Ÿš€ Built With - -- [Flutter](https://flutter.dev/) โ€” Cross-platform UI toolkit - ---- - -## ๐Ÿ“ฆ Installation - -Clone the repository: - -```bash -git clone https://github.com/yourusername/myedu-university-suite.git -cd myedu-university-suite -``` - -Get the dependencies: - -```bash -flutter pub get -``` - -> **โš ๏ธ Before Running:** -> This project requires Firebase configuration files to work properly. -> -> - **Add your Firebase files** (such as `firebase_options.dart` for Flutter and `firebase.json` for backend/functions) before running the app. -> - You can generate `firebase_options.dart` using the [FlutterFire CLI](https://firebase.flutter.dev/docs/cli/). -> - Without these files, authentication, notifications, and other cloud features will not work. - - -Run the app: - -```bash -flutter run -``` - ---- - -## ๐ŸŒ Web Admin Panel - -Looking for the admin panel? -A dedicated web-based admin panel for MyEdu is available, built with **React** for university administrators and staff. - -- **Repository:** [MyEdu-Web-Admin](https://github.com/tanvir-robin/MyEdu-Web-Admin) -- **Tech Stack:** React, Firebase - -The admin panel allows university staff to manage courses, students, results, notices, and more, all from a modern web interface. - ---- - - - -## ๐Ÿ™‹โ€โ™‚๏ธ About Me - -Hi, I'm **Tanvir Robin**, a passionate developer focused on building impactful digital solutions for education and beyond. - -- ๐Ÿ”— **Portfolio:** [tanvirrobin.dev](https://tanvirrobin.dev) -- ๐Ÿ’ผ **LinkedIn:** [linkedin.com/in/tanvir-robin](https://linkedin.com/in/tanvirrobin) - -To know more about my work, projects, and experience, please visit my portfolio or connect with me on LinkedIn! - - - -## ๐Ÿ“ง Contact - -For business inquiries or feedback, feel free to reach out at: -**๐Ÿ“ฉ contact@tanvirrobin.dev** - ---- - -## ๐Ÿ“ License - -This project is licensed under the [MIT License](LICENSE). - ---- - -> ยฉ 2025 Tanvir Robin โ€” All rights reserved. +# MyEdu - University Suite ๐ŸŽ“๐Ÿ“ฑ + +**MyEdu - University Suite** is a comprehensive university management app designed to streamline academic and administrative processes for students and institutions alike. Built with **Flutter**, this all-in-one solution brings everything a student needs, right at their fingertips. + +CleanShot 2025-08-08 at 11 52 10@2x + + + +--- + +## โœจ Features + +- **๐Ÿ“Š Dashboard** + A clean and modern dashboard overviewing important academic information. + +- **๐Ÿ“š Courses** + Browse all enrolled and available courses with full details. + +- **๐Ÿ“† Class Routines** + View weekly class schedules and plan your day accordingly. + +- **๐Ÿ“ Academic Result** + Access detailed results with **full marksheet export in PDF**. + +- **๐Ÿ’ณ Academic Fees** + View due fees and pay online via **SSLCommerz Payment Gateway**. + +- **๐Ÿงพ Payment Receipt & Notification** + Automatic **receipt generation**, plus **email and SMS** notifications after payment. + +- **๐Ÿ“ข Notice Board** + Get real-time university notices and announcements. + +- **๐Ÿ“ฒ Virtual ID Card** + NFC-powered digital ID card for easy campus access and identification. + +--- + +## ๐Ÿš€ Built With + +- [Flutter](https://flutter.dev/) โ€” Cross-platform UI toolkit + +--- + +## ๐Ÿ“ฆ Installation + +Clone the repository: + +```bash +git clone https://github.com/yourusername/myedu-university-suite.git +cd myedu-university-suite +``` + +Get the dependencies: + +```bash +flutter pub get +``` + +> **โš ๏ธ Before Running:** +> This project requires Firebase configuration files to work properly. +> +> - **Add your Firebase files** (such as `firebase_options.dart` for Flutter and `firebase.json` for backend/functions) before running the app. +> - You can generate `firebase_options.dart` using the [FlutterFire CLI](https://firebase.flutter.dev/docs/cli/). +> - Without these files, authentication, notifications, and other cloud features will not work. + + +Run the app: + +```bash +flutter run +``` + +--- + +## ๐ŸŒ Web Admin Panel + +Looking for the admin panel? +A dedicated web-based admin panel for MyEdu is available, built with **React** for university administrators and staff. + +- **Repository:** [MyEdu-Web-Admin](https://github.com/tanvir-robin/MyEdu-Web-Admin) +- **Tech Stack:** React, Firebase + +The admin panel allows university staff to manage courses, students, results, notices, and more, all from a modern web interface. + +--- + + + +## ๐Ÿ™‹โ€โ™‚๏ธ About Me + +Hi, I'm **Tanvir Robin**, a passionate developer focused on building impactful digital solutions for education and beyond. + +- ๐Ÿ”— **Portfolio:** [tanvirrobin.dev](https://tanvirrobin.dev) +- ๐Ÿ’ผ **LinkedIn:** [linkedin.com/in/tanvir-robin](https://linkedin.com/in/tanvirrobin) + +To know more about my work, projects, and experience, please visit my portfolio or connect with me on LinkedIn! + + + +## ๐Ÿ“ง Contact + +For business inquiries or feedback, feel free to reach out at: +**๐Ÿ“ฉ contact@tanvirrobin.dev** + +--- + +## ๐Ÿ“ License + +This project is licensed under the [MIT License](LICENSE). + +--- + +> ยฉ 2025 Tanvir Robin โ€” All rights reserved. diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..d4e0f0c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,28 +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 +# 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/android/.gitignore b/android/.gitignore index be3943c..c908258 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,14 +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 +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/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5f8ebbf..5dde73b 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -27,7 +27,7 @@ android { applicationId = "com.example.myedu" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 23 + minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName diff --git a/android/app/google-services.json b/android/app/google-services.json index 2541a3e..8ac5257 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -1,13 +1,13 @@ { "project_info": { - "project_number": "615444202770", - "project_id": "myedu-d90cd", - "storage_bucket": "myedu-d90cd.firebasestorage.app" + "project_number": "549482019839", + "project_id": "my-edu-23fce", + "storage_bucket": "my-edu-23fce.firebasestorage.app" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:615444202770:android:b4f13a1afdb54876285fcb", + "mobilesdk_app_id": "1:549482019839:android:2e2559c2131608f54b05ca", "android_client_info": { "package_name": "com.example.myedu" } @@ -15,7 +15,7 @@ "oauth_client": [], "api_key": [ { - "current_key": "AIzaSyBXwbpmnG5pTpQrxKsM6EAe6YPPRooo9So" + "current_key": "AIzaSyCwS9Q-XGnxqJ-oT6VSZNXl7NVXG9hDjOU" } ], "services": { diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 399f698..8ffe024 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 038348d..8dcea6c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,49 +1,49 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/myedu/MainActivity.kt b/android/app/src/main/kotlin/com/example/myedu/MainActivity.kt index 98a0428..53ffe89 100644 --- a/android/app/src/main/kotlin/com/example/myedu/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/myedu/MainActivity.kt @@ -1,5 +1,5 @@ -package com.example.myedu - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity : FlutterActivity() +package com.example.myedu + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/ic_notification.xml b/android/app/src/main/res/drawable-v21/ic_notification.xml index b2f8413..951487d 100644 --- a/android/app/src/main/res/drawable-v21/ic_notification.xml +++ b/android/app/src/main/res/drawable-v21/ic_notification.xml @@ -1,10 +1,10 @@ - - - - + + + + diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f..1cb7aa2 100644 --- a/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/android/app/src/main/res/drawable/ic_notification.xml b/android/app/src/main/res/drawable/ic_notification.xml index b2f8413..951487d 100644 --- a/android/app/src/main/res/drawable/ic_notification.xml +++ b/android/app/src/main/res/drawable/ic_notification.xml @@ -1,10 +1,10 @@ - - - - + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f..8403758 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -1,12 +1,12 @@ - - - - - - - - + + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 06952be..360a160 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef88..5fac679 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - - - - - + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index 399f698..8ffe024 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,7 +1,7 @@ - - - - + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 89176ef..2f2f3f0 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,21 +1,21 @@ -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) -} +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/android/gradle.properties b/android/gradle.properties index f018a61..475a628 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index b5fc5a7..14b4fd8 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 381b7e3..058bd29 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -1,28 +1,28 @@ -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 "8.2.2" apply false - // START: FlutterFire Configuration - id("com.google.gms.google-services") version("4.3.15") apply false - // END: FlutterFire Configuration - id("org.jetbrains.kotlin.android") version "2.2.0" apply false -} - -include(":app") +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 "8.11.1" apply false + // START: FlutterFire Configuration + id("com.google.gms.google-services") version("4.3.15") apply false + // END: FlutterFire Configuration + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/core/index.js b/core/index.js index e81477f..6683d61 100644 --- a/core/index.js +++ b/core/index.js @@ -1,19 +1,19 @@ -/** - * Import function triggers from their respective submodules: - * - * const {onCall} = require("firebase-functions/v2/https"); - * const {onDocumentWritten} = require("firebase-functions/v2/firestore"); - * - * See a full list of supported triggers at https://firebase.google.com/docs/functions - */ - -const {onRequest} = require("firebase-functions/v2/https"); -const logger = require("firebase-functions/logger"); - -// Create and deploy your first functions -// https://firebase.google.com/docs/functions/get-started - -// exports.helloWorld = onRequest((request, response) => { -// logger.info("Hello logs!", {structuredData: true}); -// response.send("Hello from Firebase!"); -// }); +/** + * Import function triggers from their respective submodules: + * + * const {onCall} = require("firebase-functions/v2/https"); + * const {onDocumentWritten} = require("firebase-functions/v2/firestore"); + * + * See a full list of supported triggers at https://firebase.google.com/docs/functions + */ + +const {onRequest} = require("firebase-functions/v2/https"); +const logger = require("firebase-functions/logger"); + +// Create and deploy your first functions +// https://firebase.google.com/docs/functions/get-started + +// exports.helloWorld = onRequest((request, response) => { +// logger.info("Hello logs!", {structuredData: true}); +// response.send("Hello from Firebase!"); +// }); diff --git a/core/package.json b/core/package.json index 392196b..9c3ea5a 100644 --- a/core/package.json +++ b/core/package.json @@ -1,23 +1,23 @@ -{ - "name": "functions", - "description": "Cloud Functions for Firebase", - "scripts": { - "serve": "firebase emulators:start --only functions", - "shell": "firebase functions:shell", - "start": "npm run shell", - "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" - }, - "engines": { - "node": "18" - }, - "main": "index.js", - "dependencies": { - "firebase-admin": "^11.8.0", - "firebase-functions": "^4.3.1" - }, - "devDependencies": { - "firebase-functions-test": "^3.1.0" - }, - "private": true -} +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "scripts": { + "serve": "firebase emulators:start --only functions", + "shell": "firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "18" + }, + "main": "index.js", + "dependencies": { + "firebase-admin": "^11.8.0", + "firebase-functions": "^4.3.1" + }, + "devDependencies": { + "firebase-functions-test": "^3.1.0" + }, + "private": true +} diff --git a/ios/.gitignore b/ios/.gitignore index 7a7f987..ad322bc 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -1,34 +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 +**/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/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 7c56964..e041d38 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -1,26 +1,26 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - App - CFBundleIdentifier - io.flutter.flutter.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - App - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1.0 - MinimumOSVersion - 12.0 - - + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index ec97fc6..553cfac 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "Generated.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index c4855bf..4b6e1f6 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "Generated.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile index 6ff8455..00badf0 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,66 +1,66 @@ -# Uncomment this line to define a global platform for your project -platform :ios, '13.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - - # Fix for flutter_inappwebview plugin issues - target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' - - # Add these lines to fix the plugin registration issues - config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ - '$(inherited)', - 'PERMISSION_CAMERA=1', - 'PERMISSION_PHOTOS=1', - 'PERMISSION_LOCATION=1', - ] - end - end - - ################ Awesome Notifications pod modification 1 ################### - awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks') - require awesome_pod_file - update_awesome_pod_build_settings(installer) - ################ Awesome Notifications pod modification 1 ################### - - ################ Awesome Notifications pod modification 2 ################### - update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root) - ################ Awesome Notifications pod modification 2 ################### +# Uncomment this line to define a global platform for your project +platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + + # Fix for flutter_inappwebview plugin issues + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' + + # Add these lines to fix the plugin registration issues + config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ + '$(inherited)', + 'PERMISSION_CAMERA=1', + 'PERMISSION_PHOTOS=1', + 'PERMISSION_LOCATION=1', + ] + end + end + + ################ Awesome Notifications pod modification 1 ################### + awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks') + require awesome_pod_file + update_awesome_pod_build_settings(installer) + ################ Awesome Notifications pod modification 1 ################### + + ################ Awesome Notifications pod modification 2 ################### + update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root) + ################ Awesome Notifications pod modification 2 ################### end \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 587f2ae..2af497c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,1556 +1,1556 @@ -PODS: - - abseil/algorithm (1.20240722.0): - - abseil/algorithm/algorithm (= 1.20240722.0) - - abseil/algorithm/container (= 1.20240722.0) - - abseil/algorithm/algorithm (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/algorithm/container (1.20240722.0): - - abseil/algorithm/algorithm - - abseil/base/config - - abseil/base/core_headers - - abseil/base/nullability - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/base (1.20240722.0): - - abseil/base/atomic_hook (= 1.20240722.0) - - abseil/base/base (= 1.20240722.0) - - abseil/base/base_internal (= 1.20240722.0) - - abseil/base/config (= 1.20240722.0) - - abseil/base/core_headers (= 1.20240722.0) - - abseil/base/cycleclock_internal (= 1.20240722.0) - - abseil/base/dynamic_annotations (= 1.20240722.0) - - abseil/base/endian (= 1.20240722.0) - - abseil/base/errno_saver (= 1.20240722.0) - - abseil/base/fast_type_id (= 1.20240722.0) - - abseil/base/log_severity (= 1.20240722.0) - - abseil/base/malloc_internal (= 1.20240722.0) - - abseil/base/no_destructor (= 1.20240722.0) - - abseil/base/nullability (= 1.20240722.0) - - abseil/base/poison (= 1.20240722.0) - - abseil/base/prefetch (= 1.20240722.0) - - abseil/base/pretty_function (= 1.20240722.0) - - abseil/base/raw_logging_internal (= 1.20240722.0) - - abseil/base/spinlock_wait (= 1.20240722.0) - - abseil/base/strerror (= 1.20240722.0) - - abseil/base/throw_delegate (= 1.20240722.0) - - abseil/base/atomic_hook (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/base/base (1.20240722.0): - - abseil/base/atomic_hook - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/cycleclock_internal - - abseil/base/dynamic_annotations - - abseil/base/log_severity - - abseil/base/nullability - - abseil/base/raw_logging_internal - - abseil/base/spinlock_wait - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/base/base_internal (1.20240722.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/base/config (1.20240722.0): - - abseil/xcprivacy - - abseil/base/core_headers (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/base/cycleclock_internal (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/xcprivacy - - abseil/base/dynamic_annotations (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/base/endian (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/nullability - - abseil/xcprivacy - - abseil/base/errno_saver (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/base/fast_type_id (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/base/log_severity (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/base/malloc_internal (1.20240722.0): - - abseil/base/base - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/raw_logging_internal - - abseil/xcprivacy - - abseil/base/no_destructor (1.20240722.0): - - abseil/base/config - - abseil/base/nullability - - abseil/xcprivacy - - abseil/base/nullability (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/base/poison (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/malloc_internal - - abseil/xcprivacy - - abseil/base/prefetch (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/base/pretty_function (1.20240722.0): - - abseil/xcprivacy - - abseil/base/raw_logging_internal (1.20240722.0): - - abseil/base/atomic_hook - - abseil/base/config - - abseil/base/core_headers - - abseil/base/errno_saver - - abseil/base/log_severity - - abseil/xcprivacy - - abseil/base/spinlock_wait (1.20240722.0): - - abseil/base/base_internal - - abseil/base/core_headers - - abseil/base/errno_saver - - abseil/xcprivacy - - abseil/base/strerror (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/errno_saver - - abseil/xcprivacy - - abseil/base/throw_delegate (1.20240722.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/xcprivacy - - abseil/cleanup/cleanup (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/cleanup/cleanup_internal - - abseil/xcprivacy - - abseil/cleanup/cleanup_internal (1.20240722.0): - - abseil/base/base_internal - - abseil/base/core_headers - - abseil/utility/utility - - abseil/xcprivacy - - abseil/container/common (1.20240722.0): - - abseil/meta/type_traits - - abseil/types/optional - - abseil/xcprivacy - - abseil/container/common_policy_traits (1.20240722.0): - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/container/compressed_tuple (1.20240722.0): - - abseil/utility/utility - - abseil/xcprivacy - - abseil/container/container_memory (1.20240722.0): - - abseil/base/config - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/utility/utility - - abseil/xcprivacy - - abseil/container/fixed_array (1.20240722.0): - - abseil/algorithm/algorithm - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/throw_delegate - - abseil/container/compressed_tuple - - abseil/memory/memory - - abseil/xcprivacy - - abseil/container/flat_hash_map (1.20240722.0): - - abseil/algorithm/container - - abseil/base/core_headers - - abseil/container/container_memory - - abseil/container/hash_container_defaults - - abseil/container/raw_hash_map - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/container/flat_hash_set (1.20240722.0): - - abseil/algorithm/container - - abseil/base/core_headers - - abseil/container/container_memory - - abseil/container/hash_container_defaults - - abseil/container/raw_hash_set - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/container/hash_container_defaults (1.20240722.0): - - abseil/base/config - - abseil/container/hash_function_defaults - - abseil/xcprivacy - - abseil/container/hash_function_defaults (1.20240722.0): - - abseil/base/config - - abseil/container/common - - abseil/hash/hash - - abseil/meta/type_traits - - abseil/strings/cord - - abseil/strings/strings - - abseil/xcprivacy - - abseil/container/hash_policy_traits (1.20240722.0): - - abseil/container/common_policy_traits - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/container/hashtable_debug_hooks (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/container/hashtablez_sampler (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/no_destructor - - abseil/base/raw_logging_internal - - abseil/debugging/stacktrace - - abseil/memory/memory - - abseil/profiling/exponential_biased - - abseil/profiling/sample_recorder - - abseil/synchronization/synchronization - - abseil/time/time - - abseil/utility/utility - - abseil/xcprivacy - - abseil/container/inlined_vector (1.20240722.0): - - abseil/algorithm/algorithm - - abseil/base/core_headers - - abseil/base/throw_delegate - - abseil/container/inlined_vector_internal - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/container/inlined_vector_internal (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/container/compressed_tuple - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/types/span - - abseil/xcprivacy - - abseil/container/layout (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/debugging/demangle_internal - - abseil/meta/type_traits - - abseil/strings/strings - - abseil/types/span - - abseil/utility/utility - - abseil/xcprivacy - - abseil/container/raw_hash_map (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/throw_delegate - - abseil/container/container_memory - - abseil/container/raw_hash_set - - abseil/xcprivacy - - abseil/container/raw_hash_set (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/endian - - abseil/base/prefetch - - abseil/base/raw_logging_internal - - abseil/container/common - - abseil/container/compressed_tuple - - abseil/container/container_memory - - abseil/container/hash_policy_traits - - abseil/container/hashtable_debug_hooks - - abseil/container/hashtablez_sampler - - abseil/hash/hash - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/utility/utility - - abseil/xcprivacy - - abseil/crc/cpu_detect (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/xcprivacy - - abseil/crc/crc32c (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/prefetch - - abseil/crc/cpu_detect - - abseil/crc/crc_internal - - abseil/crc/non_temporal_memcpy - - abseil/strings/str_format - - abseil/strings/strings - - abseil/xcprivacy - - abseil/crc/crc_cord_state (1.20240722.0): - - abseil/base/config - - abseil/base/no_destructor - - abseil/crc/crc32c - - abseil/numeric/bits - - abseil/xcprivacy - - abseil/crc/crc_internal (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/prefetch - - abseil/base/raw_logging_internal - - abseil/crc/cpu_detect - - abseil/memory/memory - - abseil/numeric/bits - - abseil/xcprivacy - - abseil/crc/non_temporal_arm_intrinsics (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/crc/non_temporal_memcpy (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/crc/non_temporal_arm_intrinsics - - abseil/xcprivacy - - abseil/debugging/bounded_utf8_length_sequence (1.20240722.0): - - abseil/base/config - - abseil/numeric/bits - - abseil/xcprivacy - - abseil/debugging/debugging_internal (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/errno_saver - - abseil/base/raw_logging_internal - - abseil/xcprivacy - - abseil/debugging/decode_rust_punycode (1.20240722.0): - - abseil/base/config - - abseil/base/nullability - - abseil/debugging/bounded_utf8_length_sequence - - abseil/debugging/utf8_for_code_point - - abseil/xcprivacy - - abseil/debugging/demangle_internal (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/nullability - - abseil/debugging/demangle_rust - - abseil/numeric/bits - - abseil/xcprivacy - - abseil/debugging/demangle_rust (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/debugging/decode_rust_punycode - - abseil/xcprivacy - - abseil/debugging/examine_stack (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/debugging/stacktrace - - abseil/debugging/symbolize - - abseil/xcprivacy - - abseil/debugging/stacktrace (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/raw_logging_internal - - abseil/debugging/debugging_internal - - abseil/xcprivacy - - abseil/debugging/symbolize (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/malloc_internal - - abseil/base/raw_logging_internal - - abseil/debugging/debugging_internal - - abseil/debugging/demangle_internal - - abseil/strings/strings - - abseil/xcprivacy - - abseil/debugging/utf8_for_code_point (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/flags/commandlineflag (1.20240722.0): - - abseil/base/config - - abseil/base/fast_type_id - - abseil/flags/commandlineflag_internal - - abseil/strings/strings - - abseil/types/optional - - abseil/xcprivacy - - abseil/flags/commandlineflag_internal (1.20240722.0): - - abseil/base/config - - abseil/base/fast_type_id - - abseil/xcprivacy - - abseil/flags/config (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/flags/path_util - - abseil/flags/program_name - - abseil/strings/strings - - abseil/synchronization/synchronization - - abseil/xcprivacy - - abseil/flags/flag (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/flags/commandlineflag - - abseil/flags/config - - abseil/flags/flag_internal - - abseil/flags/reflection - - abseil/strings/strings - - abseil/xcprivacy - - abseil/flags/flag_internal (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/flags/commandlineflag - - abseil/flags/commandlineflag_internal - - abseil/flags/config - - abseil/flags/marshalling - - abseil/flags/reflection - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/strings/strings - - abseil/synchronization/synchronization - - abseil/utility/utility - - abseil/xcprivacy - - abseil/flags/marshalling (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/numeric/int128 - - abseil/strings/str_format - - abseil/strings/strings - - abseil/types/optional - - abseil/xcprivacy - - abseil/flags/path_util (1.20240722.0): - - abseil/base/config - - abseil/strings/strings - - abseil/xcprivacy - - abseil/flags/private_handle_accessor (1.20240722.0): - - abseil/base/config - - abseil/flags/commandlineflag - - abseil/flags/commandlineflag_internal - - abseil/strings/strings - - abseil/xcprivacy - - abseil/flags/program_name (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/flags/path_util - - abseil/strings/strings - - abseil/synchronization/synchronization - - abseil/xcprivacy - - abseil/flags/reflection (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/no_destructor - - abseil/container/flat_hash_map - - abseil/flags/commandlineflag - - abseil/flags/commandlineflag_internal - - abseil/flags/config - - abseil/flags/private_handle_accessor - - abseil/strings/strings - - abseil/synchronization/synchronization - - abseil/xcprivacy - - abseil/functional/any_invocable (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/utility/utility - - abseil/xcprivacy - - abseil/functional/bind_front (1.20240722.0): - - abseil/base/base_internal - - abseil/container/compressed_tuple - - abseil/meta/type_traits - - abseil/utility/utility - - abseil/xcprivacy - - abseil/functional/function_ref (1.20240722.0): - - abseil/base/base_internal - - abseil/base/core_headers - - abseil/functional/any_invocable - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/hash/city (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/xcprivacy - - abseil/hash/hash (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/container/fixed_array - - abseil/functional/function_ref - - abseil/hash/city - - abseil/hash/low_level_hash - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/strings/strings - - abseil/types/optional - - abseil/types/variant - - abseil/utility/utility - - abseil/xcprivacy - - abseil/hash/low_level_hash (1.20240722.0): - - abseil/base/config - - abseil/base/endian - - abseil/base/prefetch - - abseil/numeric/int128 - - abseil/xcprivacy - - abseil/log/absl_check (1.20240722.0): - - abseil/log/internal/check_impl - - abseil/xcprivacy - - abseil/log/absl_log (1.20240722.0): - - abseil/log/internal/log_impl - - abseil/xcprivacy - - abseil/log/absl_vlog_is_on (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/log/internal/vlog_config - - abseil/strings/strings - - abseil/xcprivacy - - abseil/log/check (1.20240722.0): - - abseil/log/internal/check_impl - - abseil/log/internal/check_op - - abseil/log/internal/conditions - - abseil/log/internal/log_message - - abseil/log/internal/strip - - abseil/xcprivacy - - abseil/log/globals (1.20240722.0): - - abseil/base/atomic_hook - - abseil/base/config - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/base/raw_logging_internal - - abseil/hash/hash - - abseil/log/internal/vlog_config - - abseil/strings/strings - - abseil/xcprivacy - - abseil/log/internal/append_truncated (1.20240722.0): - - abseil/base/config - - abseil/strings/strings - - abseil/types/span - - abseil/xcprivacy - - abseil/log/internal/check_impl (1.20240722.0): - - abseil/base/core_headers - - abseil/log/internal/check_op - - abseil/log/internal/conditions - - abseil/log/internal/log_message - - abseil/log/internal/strip - - abseil/xcprivacy - - abseil/log/internal/check_op (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/log/internal/nullguard - - abseil/log/internal/nullstream - - abseil/log/internal/strip - - abseil/strings/strings - - abseil/xcprivacy - - abseil/log/internal/conditions (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/log/internal/voidify - - abseil/xcprivacy - - abseil/log/internal/config (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/log/internal/fnmatch (1.20240722.0): - - abseil/base/config - - abseil/strings/strings - - abseil/xcprivacy - - abseil/log/internal/format (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/log/internal/append_truncated - - abseil/log/internal/config - - abseil/log/internal/globals - - abseil/strings/str_format - - abseil/strings/strings - - abseil/time/time - - abseil/types/span - - abseil/xcprivacy - - abseil/log/internal/globals (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/base/raw_logging_internal - - abseil/strings/strings - - abseil/time/time - - abseil/xcprivacy - - abseil/log/internal/log_impl (1.20240722.0): - - abseil/log/absl_vlog_is_on - - abseil/log/internal/conditions - - abseil/log/internal/log_message - - abseil/log/internal/strip - - abseil/xcprivacy - - abseil/log/internal/log_message (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/errno_saver - - abseil/base/log_severity - - abseil/base/raw_logging_internal - - abseil/base/strerror - - abseil/container/inlined_vector - - abseil/debugging/examine_stack - - abseil/log/globals - - abseil/log/internal/append_truncated - - abseil/log/internal/format - - abseil/log/internal/globals - - abseil/log/internal/log_sink_set - - abseil/log/internal/nullguard - - abseil/log/internal/proto - - abseil/log/log_entry - - abseil/log/log_sink - - abseil/log/log_sink_registry - - abseil/memory/memory - - abseil/strings/strings - - abseil/time/time - - abseil/types/span - - abseil/xcprivacy - - abseil/log/internal/log_sink_set (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/base/no_destructor - - abseil/base/raw_logging_internal - - abseil/cleanup/cleanup - - abseil/log/globals - - abseil/log/internal/config - - abseil/log/internal/globals - - abseil/log/log_entry - - abseil/log/log_sink - - abseil/strings/strings - - abseil/synchronization/synchronization - - abseil/types/span - - abseil/xcprivacy - - abseil/log/internal/nullguard (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/log/internal/nullstream (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/strings/strings - - abseil/xcprivacy - - abseil/log/internal/proto (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/strings/strings - - abseil/types/span - - abseil/xcprivacy - - abseil/log/internal/strip (1.20240722.0): - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/log/internal/log_message - - abseil/log/internal/nullstream - - abseil/xcprivacy - - abseil/log/internal/vlog_config (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/no_destructor - - abseil/log/internal/fnmatch - - abseil/memory/memory - - abseil/strings/strings - - abseil/synchronization/synchronization - - abseil/types/optional - - abseil/xcprivacy - - abseil/log/internal/voidify (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/log/log (1.20240722.0): - - abseil/log/internal/log_impl - - abseil/log/vlog_is_on - - abseil/xcprivacy - - abseil/log/log_entry (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/log_severity - - abseil/log/internal/config - - abseil/strings/strings - - abseil/time/time - - abseil/types/span - - abseil/xcprivacy - - abseil/log/log_sink (1.20240722.0): - - abseil/base/config - - abseil/log/log_entry - - abseil/xcprivacy - - abseil/log/log_sink_registry (1.20240722.0): - - abseil/base/config - - abseil/log/internal/log_sink_set - - abseil/log/log_sink - - abseil/xcprivacy - - abseil/log/vlog_is_on (1.20240722.0): - - abseil/log/absl_vlog_is_on - - abseil/xcprivacy - - abseil/memory (1.20240722.0): - - abseil/memory/memory (= 1.20240722.0) - - abseil/memory/memory (1.20240722.0): - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/meta (1.20240722.0): - - abseil/meta/type_traits (= 1.20240722.0) - - abseil/meta/type_traits (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/numeric/bits (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/numeric/int128 (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/numeric/bits - - abseil/types/compare - - abseil/xcprivacy - - abseil/numeric/representation (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/profiling/exponential_biased (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/xcprivacy - - abseil/profiling/sample_recorder (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/synchronization/synchronization - - abseil/time/time - - abseil/xcprivacy - - abseil/random/bit_gen_ref (1.20240722.0): - - abseil/base/core_headers - - abseil/base/fast_type_id - - abseil/meta/type_traits - - abseil/random/internal/distribution_caller - - abseil/random/internal/fast_uniform_bits - - abseil/random/random - - abseil/xcprivacy - - abseil/random/distributions (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/random/internal/distribution_caller - - abseil/random/internal/fast_uniform_bits - - abseil/random/internal/fastmath - - abseil/random/internal/generate_real - - abseil/random/internal/iostream_state_saver - - abseil/random/internal/traits - - abseil/random/internal/uniform_helper - - abseil/random/internal/wide_multiply - - abseil/strings/strings - - abseil/xcprivacy - - abseil/random/internal/distribution_caller (1.20240722.0): - - abseil/base/config - - abseil/base/fast_type_id - - abseil/utility/utility - - abseil/xcprivacy - - abseil/random/internal/fast_uniform_bits (1.20240722.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/random/internal/traits - - abseil/xcprivacy - - abseil/random/internal/fastmath (1.20240722.0): - - abseil/numeric/bits - - abseil/xcprivacy - - abseil/random/internal/generate_real (1.20240722.0): - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/random/internal/fastmath - - abseil/random/internal/traits - - abseil/xcprivacy - - abseil/random/internal/iostream_state_saver (1.20240722.0): - - abseil/meta/type_traits - - abseil/numeric/int128 - - abseil/xcprivacy - - abseil/random/internal/nonsecure_base (1.20240722.0): - - abseil/base/core_headers - - abseil/container/inlined_vector - - abseil/meta/type_traits - - abseil/random/internal/pool_urbg - - abseil/random/internal/salted_seed_seq - - abseil/random/internal/seed_material - - abseil/types/span - - abseil/xcprivacy - - abseil/random/internal/pcg_engine (1.20240722.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/random/internal/fastmath - - abseil/random/internal/iostream_state_saver - - abseil/xcprivacy - - abseil/random/internal/platform (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/random/internal/pool_urbg (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/random/internal/randen - - abseil/random/internal/seed_material - - abseil/random/internal/traits - - abseil/random/seed_gen_exception - - abseil/types/span - - abseil/xcprivacy - - abseil/random/internal/randen (1.20240722.0): - - abseil/base/raw_logging_internal - - abseil/random/internal/platform - - abseil/random/internal/randen_hwaes - - abseil/random/internal/randen_slow - - abseil/xcprivacy - - abseil/random/internal/randen_engine (1.20240722.0): - - abseil/base/endian - - abseil/meta/type_traits - - abseil/random/internal/iostream_state_saver - - abseil/random/internal/randen - - abseil/xcprivacy - - abseil/random/internal/randen_hwaes (1.20240722.0): - - abseil/base/config - - abseil/random/internal/platform - - abseil/random/internal/randen_hwaes_impl - - abseil/xcprivacy - - abseil/random/internal/randen_hwaes_impl (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/numeric/int128 - - abseil/random/internal/platform - - abseil/xcprivacy - - abseil/random/internal/randen_slow (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/numeric/int128 - - abseil/random/internal/platform - - abseil/xcprivacy - - abseil/random/internal/salted_seed_seq (1.20240722.0): - - abseil/container/inlined_vector - - abseil/meta/type_traits - - abseil/random/internal/seed_material - - abseil/types/optional - - abseil/types/span - - abseil/xcprivacy - - abseil/random/internal/seed_material (1.20240722.0): - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/raw_logging_internal - - abseil/random/internal/fast_uniform_bits - - abseil/strings/strings - - abseil/types/optional - - abseil/types/span - - abseil/xcprivacy - - abseil/random/internal/traits (1.20240722.0): - - abseil/base/config - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/xcprivacy - - abseil/random/internal/uniform_helper (1.20240722.0): - - abseil/base/config - - abseil/meta/type_traits - - abseil/random/internal/traits - - abseil/xcprivacy - - abseil/random/internal/wide_multiply (1.20240722.0): - - abseil/base/config - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/random/internal/traits - - abseil/xcprivacy - - abseil/random/random (1.20240722.0): - - abseil/random/distributions - - abseil/random/internal/nonsecure_base - - abseil/random/internal/pcg_engine - - abseil/random/internal/pool_urbg - - abseil/random/internal/randen_engine - - abseil/random/seed_sequences - - abseil/xcprivacy - - abseil/random/seed_gen_exception (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/random/seed_sequences (1.20240722.0): - - abseil/base/config - - abseil/base/nullability - - abseil/random/internal/pool_urbg - - abseil/random/internal/salted_seed_seq - - abseil/random/internal/seed_material - - abseil/random/seed_gen_exception - - abseil/strings/string_view - - abseil/types/span - - abseil/xcprivacy - - abseil/status/status (1.20240722.0): - - abseil/base/atomic_hook - - abseil/base/config - - abseil/base/core_headers - - abseil/base/no_destructor - - abseil/base/nullability - - abseil/base/raw_logging_internal - - abseil/base/strerror - - abseil/container/inlined_vector - - abseil/debugging/stacktrace - - abseil/debugging/symbolize - - abseil/functional/function_ref - - abseil/memory/memory - - abseil/strings/cord - - abseil/strings/str_format - - abseil/strings/strings - - abseil/types/optional - - abseil/types/span - - abseil/xcprivacy - - abseil/status/statusor (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/nullability - - abseil/base/raw_logging_internal - - abseil/meta/type_traits - - abseil/status/status - - abseil/strings/has_ostream_operator - - abseil/strings/str_format - - abseil/strings/strings - - abseil/types/variant - - abseil/utility/utility - - abseil/xcprivacy - - abseil/strings/charset (1.20240722.0): - - abseil/base/core_headers - - abseil/strings/string_view - - abseil/xcprivacy - - abseil/strings/cord (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/nullability - - abseil/base/raw_logging_internal - - abseil/container/inlined_vector - - abseil/crc/crc32c - - abseil/crc/crc_cord_state - - abseil/functional/function_ref - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/strings/cord_internal - - abseil/strings/cordz_functions - - abseil/strings/cordz_info - - abseil/strings/cordz_statistics - - abseil/strings/cordz_update_scope - - abseil/strings/cordz_update_tracker - - abseil/strings/internal - - abseil/strings/strings - - abseil/types/compare - - abseil/types/optional - - abseil/types/span - - abseil/xcprivacy - - abseil/strings/cord_internal (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/base/throw_delegate - - abseil/container/compressed_tuple - - abseil/container/container_memory - - abseil/container/inlined_vector - - abseil/container/layout - - abseil/crc/crc_cord_state - - abseil/functional/function_ref - - abseil/meta/type_traits - - abseil/strings/strings - - abseil/types/span - - abseil/xcprivacy - - abseil/strings/cordz_functions (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/profiling/exponential_biased - - abseil/xcprivacy - - abseil/strings/cordz_handle (1.20240722.0): - - abseil/base/config - - abseil/base/no_destructor - - abseil/base/raw_logging_internal - - abseil/synchronization/synchronization - - abseil/xcprivacy - - abseil/strings/cordz_info (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/container/inlined_vector - - abseil/debugging/stacktrace - - abseil/strings/cord_internal - - abseil/strings/cordz_functions - - abseil/strings/cordz_handle - - abseil/strings/cordz_statistics - - abseil/strings/cordz_update_tracker - - abseil/synchronization/synchronization - - abseil/time/time - - abseil/types/span - - abseil/xcprivacy - - abseil/strings/cordz_statistics (1.20240722.0): - - abseil/base/config - - abseil/strings/cordz_update_tracker - - abseil/xcprivacy - - abseil/strings/cordz_update_scope (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/strings/cord_internal - - abseil/strings/cordz_info - - abseil/strings/cordz_update_tracker - - abseil/xcprivacy - - abseil/strings/cordz_update_tracker (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/strings/has_ostream_operator (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/strings/internal (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/raw_logging_internal - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/strings/str_format (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/nullability - - abseil/strings/str_format_internal - - abseil/strings/string_view - - abseil/types/span - - abseil/xcprivacy - - abseil/strings/str_format_internal (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/container/fixed_array - - abseil/container/inlined_vector - - abseil/functional/function_ref - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/numeric/representation - - abseil/strings/strings - - abseil/types/optional - - abseil/types/span - - abseil/utility/utility - - abseil/xcprivacy - - abseil/strings/string_view (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/nullability - - abseil/base/throw_delegate - - abseil/xcprivacy - - abseil/strings/strings (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/endian - - abseil/base/nullability - - abseil/base/raw_logging_internal - - abseil/base/throw_delegate - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/numeric/bits - - abseil/numeric/int128 - - abseil/strings/charset - - abseil/strings/internal - - abseil/strings/string_view - - abseil/xcprivacy - - abseil/synchronization/graphcycles_internal (1.20240722.0): - - abseil/base/base - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/malloc_internal - - abseil/base/raw_logging_internal - - abseil/xcprivacy - - abseil/synchronization/kernel_timeout_internal (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/time/time - - abseil/xcprivacy - - abseil/synchronization/synchronization (1.20240722.0): - - abseil/base/atomic_hook - - abseil/base/base - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/dynamic_annotations - - abseil/base/malloc_internal - - abseil/base/raw_logging_internal - - abseil/debugging/stacktrace - - abseil/debugging/symbolize - - abseil/synchronization/graphcycles_internal - - abseil/synchronization/kernel_timeout_internal - - abseil/time/time - - abseil/xcprivacy - - abseil/time (1.20240722.0): - - abseil/time/internal (= 1.20240722.0) - - abseil/time/time (= 1.20240722.0) - - abseil/time/internal (1.20240722.0): - - abseil/time/internal/cctz (= 1.20240722.0) - - abseil/time/internal/cctz (1.20240722.0): - - abseil/time/internal/cctz/civil_time (= 1.20240722.0) - - abseil/time/internal/cctz/time_zone (= 1.20240722.0) - - abseil/time/internal/cctz/civil_time (1.20240722.0): - - abseil/base/config - - abseil/xcprivacy - - abseil/time/internal/cctz/time_zone (1.20240722.0): - - abseil/base/config - - abseil/time/internal/cctz/civil_time - - abseil/xcprivacy - - abseil/time/time (1.20240722.0): - - abseil/base/base - - abseil/base/config - - abseil/base/core_headers - - abseil/base/raw_logging_internal - - abseil/numeric/int128 - - abseil/strings/strings - - abseil/time/internal/cctz/civil_time - - abseil/time/internal/cctz/time_zone - - abseil/types/optional - - abseil/xcprivacy - - abseil/types (1.20240722.0): - - abseil/types/any (= 1.20240722.0) - - abseil/types/bad_any_cast (= 1.20240722.0) - - abseil/types/bad_any_cast_impl (= 1.20240722.0) - - abseil/types/bad_optional_access (= 1.20240722.0) - - abseil/types/bad_variant_access (= 1.20240722.0) - - abseil/types/compare (= 1.20240722.0) - - abseil/types/optional (= 1.20240722.0) - - abseil/types/span (= 1.20240722.0) - - abseil/types/variant (= 1.20240722.0) - - abseil/types/any (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/base/fast_type_id - - abseil/meta/type_traits - - abseil/types/bad_any_cast - - abseil/utility/utility - - abseil/xcprivacy - - abseil/types/bad_any_cast (1.20240722.0): - - abseil/base/config - - abseil/types/bad_any_cast_impl - - abseil/xcprivacy - - abseil/types/bad_any_cast_impl (1.20240722.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/xcprivacy - - abseil/types/bad_optional_access (1.20240722.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/xcprivacy - - abseil/types/bad_variant_access (1.20240722.0): - - abseil/base/config - - abseil/base/raw_logging_internal - - abseil/xcprivacy - - abseil/types/compare (1.20240722.0): - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/types/optional (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/base/nullability - - abseil/memory/memory - - abseil/meta/type_traits - - abseil/types/bad_optional_access - - abseil/utility/utility - - abseil/xcprivacy - - abseil/types/span (1.20240722.0): - - abseil/algorithm/algorithm - - abseil/base/core_headers - - abseil/base/nullability - - abseil/base/throw_delegate - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/types/variant (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/base/core_headers - - abseil/meta/type_traits - - abseil/types/bad_variant_access - - abseil/utility/utility - - abseil/xcprivacy - - abseil/utility/utility (1.20240722.0): - - abseil/base/base_internal - - abseil/base/config - - abseil/meta/type_traits - - abseil/xcprivacy - - abseil/xcprivacy (1.20240722.0) - - awesome_notifications (0.10.0): - - Flutter - - IosAwnCore (~> 0.10.0) - - BoringSSL-GRPC (0.0.37): - - BoringSSL-GRPC/Implementation (= 0.0.37) - - BoringSSL-GRPC/Interface (= 0.0.37) - - BoringSSL-GRPC/Implementation (0.0.37): - - BoringSSL-GRPC/Interface (= 0.0.37) - - BoringSSL-GRPC/Interface (0.0.37) - - cloud_firestore (5.6.8): - - Firebase/Firestore (= 11.10.0) - - firebase_core - - Flutter - - Firebase/Auth (11.10.0): - - Firebase/CoreOnly - - FirebaseAuth (~> 11.10.0) - - Firebase/CoreOnly (11.10.0): - - FirebaseCore (~> 11.10.0) - - Firebase/Firestore (11.10.0): - - Firebase/CoreOnly - - FirebaseFirestore (~> 11.10.0) - - Firebase/Messaging (11.10.0): - - Firebase/CoreOnly - - FirebaseMessaging (~> 11.10.0) - - Firebase/Storage (11.10.0): - - Firebase/CoreOnly - - FirebaseStorage (~> 11.10.0) - - firebase_auth (5.5.4): - - Firebase/Auth (= 11.10.0) - - firebase_core - - Flutter - - firebase_core (3.13.1): - - Firebase/CoreOnly (= 11.10.0) - - Flutter - - firebase_messaging (15.2.6): - - Firebase/Messaging (= 11.10.0) - - firebase_core - - Flutter - - firebase_storage (12.4.6): - - Firebase/Storage (= 11.10.0) - - firebase_core - - Flutter - - FirebaseAppCheckInterop (11.13.0) - - FirebaseAuth (11.10.0): - - FirebaseAppCheckInterop (~> 11.0) - - FirebaseAuthInterop (~> 11.0) - - FirebaseCore (~> 11.10.0) - - FirebaseCoreExtension (~> 11.10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - - GoogleUtilities/Environment (~> 8.0) - - GTMSessionFetcher/Core (< 5.0, >= 3.4) - - RecaptchaInterop (~> 101.0) - - FirebaseAuthInterop (11.13.0) - - FirebaseCore (11.10.0): - - FirebaseCoreInternal (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Logger (~> 8.0) - - FirebaseCoreExtension (11.10.0): - - FirebaseCore (~> 11.10.0) - - FirebaseCoreInternal (11.10.0): - - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseFirestore (11.10.0): - - FirebaseCore (~> 11.10.0) - - FirebaseCoreExtension (~> 11.10.0) - - FirebaseFirestoreInternal (= 11.10.0) - - FirebaseSharedSwift (~> 11.0) - - FirebaseFirestoreInternal (11.10.0): - - abseil/algorithm (~> 1.20240722.0) - - abseil/base (~> 1.20240722.0) - - abseil/container/flat_hash_map (~> 1.20240722.0) - - abseil/memory (~> 1.20240722.0) - - abseil/meta (~> 1.20240722.0) - - abseil/strings/strings (~> 1.20240722.0) - - abseil/time (~> 1.20240722.0) - - abseil/types (~> 1.20240722.0) - - FirebaseAppCheckInterop (~> 11.0) - - FirebaseCore (~> 11.10.0) - - "gRPC-C++ (~> 1.69.0)" - - gRPC-Core (~> 1.69.0) - - leveldb-library (~> 1.22) - - nanopb (~> 3.30910.0) - - FirebaseInstallations (11.10.0): - - FirebaseCore (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) - - PromisesObjC (~> 2.4) - - FirebaseMessaging (11.10.0): - - FirebaseCore (~> 11.10.0) - - FirebaseInstallations (~> 11.0) - - GoogleDataTransport (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Reachability (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) - - nanopb (~> 3.30910.0) - - FirebaseSharedSwift (11.13.0) - - FirebaseStorage (11.10.0): - - FirebaseAppCheckInterop (~> 11.0) - - FirebaseAuthInterop (~> 11.0) - - FirebaseCore (~> 11.10.0) - - FirebaseCoreExtension (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GTMSessionFetcher/Core (< 5.0, >= 3.4) - - Flutter (1.0.0) - - flutter_sslcommerz (2.3.9): - - Flutter - - SSLCommerzSDK - - GoogleDataTransport (10.1.0): - - nanopb (~> 3.30910.0) - - PromisesObjC (~> 2.4) - - GoogleUtilities/AppDelegateSwizzler (8.1.0): - - GoogleUtilities/Environment - - GoogleUtilities/Logger - - GoogleUtilities/Network - - GoogleUtilities/Privacy - - GoogleUtilities/Environment (8.1.0): - - GoogleUtilities/Privacy - - GoogleUtilities/Logger (8.1.0): - - GoogleUtilities/Environment - - GoogleUtilities/Privacy - - GoogleUtilities/Network (8.1.0): - - GoogleUtilities/Logger - - "GoogleUtilities/NSData+zlib" - - GoogleUtilities/Privacy - - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (8.1.0)": - - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (8.1.0) - - GoogleUtilities/Reachability (8.1.0): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - GoogleUtilities/UserDefaults (8.1.0): - - GoogleUtilities/Logger - - GoogleUtilities/Privacy - - "gRPC-C++ (1.69.0)": - - "gRPC-C++/Implementation (= 1.69.0)" - - "gRPC-C++/Interface (= 1.69.0)" - - "gRPC-C++/Implementation (1.69.0)": - - abseil/algorithm/container (~> 1.20240722.0) - - abseil/base/base (~> 1.20240722.0) - - abseil/base/config (~> 1.20240722.0) - - abseil/base/core_headers (~> 1.20240722.0) - - abseil/base/log_severity (~> 1.20240722.0) - - abseil/base/no_destructor (~> 1.20240722.0) - - abseil/cleanup/cleanup (~> 1.20240722.0) - - abseil/container/flat_hash_map (~> 1.20240722.0) - - abseil/container/flat_hash_set (~> 1.20240722.0) - - abseil/container/inlined_vector (~> 1.20240722.0) - - abseil/flags/flag (~> 1.20240722.0) - - abseil/flags/marshalling (~> 1.20240722.0) - - abseil/functional/any_invocable (~> 1.20240722.0) - - abseil/functional/bind_front (~> 1.20240722.0) - - abseil/functional/function_ref (~> 1.20240722.0) - - abseil/hash/hash (~> 1.20240722.0) - - abseil/log/absl_check (~> 1.20240722.0) - - abseil/log/absl_log (~> 1.20240722.0) - - abseil/log/check (~> 1.20240722.0) - - abseil/log/globals (~> 1.20240722.0) - - abseil/log/log (~> 1.20240722.0) - - abseil/memory/memory (~> 1.20240722.0) - - abseil/meta/type_traits (~> 1.20240722.0) - - abseil/numeric/bits (~> 1.20240722.0) - - abseil/random/bit_gen_ref (~> 1.20240722.0) - - abseil/random/distributions (~> 1.20240722.0) - - abseil/random/random (~> 1.20240722.0) - - abseil/status/status (~> 1.20240722.0) - - abseil/status/statusor (~> 1.20240722.0) - - abseil/strings/cord (~> 1.20240722.0) - - abseil/strings/str_format (~> 1.20240722.0) - - abseil/strings/strings (~> 1.20240722.0) - - abseil/synchronization/synchronization (~> 1.20240722.0) - - abseil/time/time (~> 1.20240722.0) - - abseil/types/optional (~> 1.20240722.0) - - abseil/types/span (~> 1.20240722.0) - - abseil/types/variant (~> 1.20240722.0) - - abseil/utility/utility (~> 1.20240722.0) - - "gRPC-C++/Interface (= 1.69.0)" - - "gRPC-C++/Privacy (= 1.69.0)" - - gRPC-Core (= 1.69.0) - - "gRPC-C++/Interface (1.69.0)" - - "gRPC-C++/Privacy (1.69.0)" - - gRPC-Core (1.69.0): - - gRPC-Core/Implementation (= 1.69.0) - - gRPC-Core/Interface (= 1.69.0) - - gRPC-Core/Implementation (1.69.0): - - abseil/algorithm/container (~> 1.20240722.0) - - abseil/base/base (~> 1.20240722.0) - - abseil/base/config (~> 1.20240722.0) - - abseil/base/core_headers (~> 1.20240722.0) - - abseil/base/log_severity (~> 1.20240722.0) - - abseil/base/no_destructor (~> 1.20240722.0) - - abseil/cleanup/cleanup (~> 1.20240722.0) - - abseil/container/flat_hash_map (~> 1.20240722.0) - - abseil/container/flat_hash_set (~> 1.20240722.0) - - abseil/container/inlined_vector (~> 1.20240722.0) - - abseil/flags/flag (~> 1.20240722.0) - - abseil/flags/marshalling (~> 1.20240722.0) - - abseil/functional/any_invocable (~> 1.20240722.0) - - abseil/functional/bind_front (~> 1.20240722.0) - - abseil/functional/function_ref (~> 1.20240722.0) - - abseil/hash/hash (~> 1.20240722.0) - - abseil/log/check (~> 1.20240722.0) - - abseil/log/globals (~> 1.20240722.0) - - abseil/log/log (~> 1.20240722.0) - - abseil/memory/memory (~> 1.20240722.0) - - abseil/meta/type_traits (~> 1.20240722.0) - - abseil/numeric/bits (~> 1.20240722.0) - - abseil/random/bit_gen_ref (~> 1.20240722.0) - - abseil/random/distributions (~> 1.20240722.0) - - abseil/random/random (~> 1.20240722.0) - - abseil/status/status (~> 1.20240722.0) - - abseil/status/statusor (~> 1.20240722.0) - - abseil/strings/cord (~> 1.20240722.0) - - abseil/strings/str_format (~> 1.20240722.0) - - abseil/strings/strings (~> 1.20240722.0) - - abseil/synchronization/synchronization (~> 1.20240722.0) - - abseil/time/time (~> 1.20240722.0) - - abseil/types/optional (~> 1.20240722.0) - - abseil/types/span (~> 1.20240722.0) - - abseil/types/variant (~> 1.20240722.0) - - abseil/utility/utility (~> 1.20240722.0) - - BoringSSL-GRPC (= 0.0.37) - - gRPC-Core/Interface (= 1.69.0) - - gRPC-Core/Privacy (= 1.69.0) - - gRPC-Core/Interface (1.69.0) - - gRPC-Core/Privacy (1.69.0) - - GTMSessionFetcher/Core (4.5.0) - - image_picker_ios (0.0.1): - - Flutter - - IosAwnCore (0.10.0) - - leveldb-library (1.22.6) - - nanopb (3.30910.0): - - nanopb/decode (= 3.30910.0) - - nanopb/encode (= 3.30910.0) - - nanopb/decode (3.30910.0) - - nanopb/encode (3.30910.0) - - open_file_ios (0.0.1): - - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - - printing (1.0.0): - - Flutter - - PromisesObjC (2.4.0) - - RecaptchaInterop (101.0.0) - - sqflite_darwin (0.0.4): - - Flutter - - FlutterMacOS - - SSLCommerzSDK (2.0.5) - -DEPENDENCIES: - - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`) - - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) - - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) - - Flutter (from `Flutter`) - - flutter_sslcommerz (from `.symlinks/plugins/flutter_sslcommerz/ios`) - - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - - printing (from `.symlinks/plugins/printing/ios`) - - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - -SPEC REPOS: - trunk: - - abseil - - BoringSSL-GRPC - - Firebase - - FirebaseAppCheckInterop - - FirebaseAuth - - FirebaseAuthInterop - - FirebaseCore - - FirebaseCoreExtension - - FirebaseCoreInternal - - FirebaseFirestore - - FirebaseFirestoreInternal - - FirebaseInstallations - - FirebaseMessaging - - FirebaseSharedSwift - - FirebaseStorage - - GoogleDataTransport - - GoogleUtilities - - "gRPC-C++" - - gRPC-Core - - GTMSessionFetcher - - IosAwnCore - - leveldb-library - - nanopb - - PromisesObjC - - RecaptchaInterop - - SSLCommerzSDK - -EXTERNAL SOURCES: - awesome_notifications: - :path: ".symlinks/plugins/awesome_notifications/ios" - cloud_firestore: - :path: ".symlinks/plugins/cloud_firestore/ios" - firebase_auth: - :path: ".symlinks/plugins/firebase_auth/ios" - firebase_core: - :path: ".symlinks/plugins/firebase_core/ios" - firebase_messaging: - :path: ".symlinks/plugins/firebase_messaging/ios" - firebase_storage: - :path: ".symlinks/plugins/firebase_storage/ios" - Flutter: - :path: Flutter - flutter_sslcommerz: - :path: ".symlinks/plugins/flutter_sslcommerz/ios" - image_picker_ios: - :path: ".symlinks/plugins/image_picker_ios/ios" - open_file_ios: - :path: ".symlinks/plugins/open_file_ios/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" - printing: - :path: ".symlinks/plugins/printing/ios" - sqflite_darwin: - :path: ".symlinks/plugins/sqflite_darwin/darwin" - -SPEC CHECKSUMS: - abseil: a05cc83bf02079535e17169a73c5be5ba47f714b - awesome_notifications: dd5518ff1c80be03d4f1c40f04da9d9cc2a37af5 - BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508 - cloud_firestore: 418c4911a2e4f1c38ac6dbb838ef19cdc32a515f - Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_auth: 519455ee22dea9cb8b4e05d7821f93f77db88882 - firebase_core: 3c2f323cae65c97a636a05a23b17730ef93df2cf - firebase_messaging: 456e01ff29a451c90097d0b45925551d5be0c143 - firebase_storage: a53140c25885d8de77fecf98cbaa0d9fff501598 - FirebaseAppCheckInterop: 72066489c209823649a997132bcd9269bc33a4bb - FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32 - FirebaseAuthInterop: 4fa327ec3c551a80a6929561f83af80b1dd44937 - FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 - FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac - FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 - FirebaseFirestore: 3f1488ff7739cb3c5d10e572bc4e9fcd8e8cb4ac - FirebaseFirestoreInternal: 97a2bb5f16951c77753c860d3519379702ab6f8a - FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 - FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 - FirebaseSharedSwift: aca73668bc95e8efccb618e0167eab05d19d3a75 - FirebaseStorage: e83d1b9c8a5318d46ccfb2955f0d98095e0bf598 - Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_sslcommerz: e8a42bb445dbf8a4e965c3894188411765adc2f5 - GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 - GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - "gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8 - gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330 - GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - IosAwnCore: 653786a911089012092ce831f2945cd339855a89 - leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 - nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - open_file_ios: 461db5853723763573e140de3193656f91990d9e - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - printing: 233e1b73bd1f4a05615548e9b5a324c98588640b - PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba - sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d - SSLCommerzSDK: 9997682baa0a9ae4a69fb9a2be62cd11be6fe848 - -PODFILE CHECKSUM: fdcd7799e3f3830110a9695ac577d61ab285fd7a - -COCOAPODS: 1.16.2 +PODS: + - abseil/algorithm (1.20240722.0): + - abseil/algorithm/algorithm (= 1.20240722.0) + - abseil/algorithm/container (= 1.20240722.0) + - abseil/algorithm/algorithm (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/algorithm/container (1.20240722.0): + - abseil/algorithm/algorithm + - abseil/base/config + - abseil/base/core_headers + - abseil/base/nullability + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/base (1.20240722.0): + - abseil/base/atomic_hook (= 1.20240722.0) + - abseil/base/base (= 1.20240722.0) + - abseil/base/base_internal (= 1.20240722.0) + - abseil/base/config (= 1.20240722.0) + - abseil/base/core_headers (= 1.20240722.0) + - abseil/base/cycleclock_internal (= 1.20240722.0) + - abseil/base/dynamic_annotations (= 1.20240722.0) + - abseil/base/endian (= 1.20240722.0) + - abseil/base/errno_saver (= 1.20240722.0) + - abseil/base/fast_type_id (= 1.20240722.0) + - abseil/base/log_severity (= 1.20240722.0) + - abseil/base/malloc_internal (= 1.20240722.0) + - abseil/base/no_destructor (= 1.20240722.0) + - abseil/base/nullability (= 1.20240722.0) + - abseil/base/poison (= 1.20240722.0) + - abseil/base/prefetch (= 1.20240722.0) + - abseil/base/pretty_function (= 1.20240722.0) + - abseil/base/raw_logging_internal (= 1.20240722.0) + - abseil/base/spinlock_wait (= 1.20240722.0) + - abseil/base/strerror (= 1.20240722.0) + - abseil/base/throw_delegate (= 1.20240722.0) + - abseil/base/atomic_hook (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/base/base (1.20240722.0): + - abseil/base/atomic_hook + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/cycleclock_internal + - abseil/base/dynamic_annotations + - abseil/base/log_severity + - abseil/base/nullability + - abseil/base/raw_logging_internal + - abseil/base/spinlock_wait + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/base/base_internal (1.20240722.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/base/config (1.20240722.0): + - abseil/xcprivacy + - abseil/base/core_headers (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/base/cycleclock_internal (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/xcprivacy + - abseil/base/dynamic_annotations (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/base/endian (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/nullability + - abseil/xcprivacy + - abseil/base/errno_saver (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/base/fast_type_id (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/base/log_severity (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/base/malloc_internal (1.20240722.0): + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/raw_logging_internal + - abseil/xcprivacy + - abseil/base/no_destructor (1.20240722.0): + - abseil/base/config + - abseil/base/nullability + - abseil/xcprivacy + - abseil/base/nullability (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/base/poison (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/malloc_internal + - abseil/xcprivacy + - abseil/base/prefetch (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/base/pretty_function (1.20240722.0): + - abseil/xcprivacy + - abseil/base/raw_logging_internal (1.20240722.0): + - abseil/base/atomic_hook + - abseil/base/config + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/base/log_severity + - abseil/xcprivacy + - abseil/base/spinlock_wait (1.20240722.0): + - abseil/base/base_internal + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/xcprivacy + - abseil/base/strerror (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/xcprivacy + - abseil/base/throw_delegate (1.20240722.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/xcprivacy + - abseil/cleanup/cleanup (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/cleanup/cleanup_internal + - abseil/xcprivacy + - abseil/cleanup/cleanup_internal (1.20240722.0): + - abseil/base/base_internal + - abseil/base/core_headers + - abseil/utility/utility + - abseil/xcprivacy + - abseil/container/common (1.20240722.0): + - abseil/meta/type_traits + - abseil/types/optional + - abseil/xcprivacy + - abseil/container/common_policy_traits (1.20240722.0): + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/container/compressed_tuple (1.20240722.0): + - abseil/utility/utility + - abseil/xcprivacy + - abseil/container/container_memory (1.20240722.0): + - abseil/base/config + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/utility/utility + - abseil/xcprivacy + - abseil/container/fixed_array (1.20240722.0): + - abseil/algorithm/algorithm + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/throw_delegate + - abseil/container/compressed_tuple + - abseil/memory/memory + - abseil/xcprivacy + - abseil/container/flat_hash_map (1.20240722.0): + - abseil/algorithm/container + - abseil/base/core_headers + - abseil/container/container_memory + - abseil/container/hash_container_defaults + - abseil/container/raw_hash_map + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/container/flat_hash_set (1.20240722.0): + - abseil/algorithm/container + - abseil/base/core_headers + - abseil/container/container_memory + - abseil/container/hash_container_defaults + - abseil/container/raw_hash_set + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/container/hash_container_defaults (1.20240722.0): + - abseil/base/config + - abseil/container/hash_function_defaults + - abseil/xcprivacy + - abseil/container/hash_function_defaults (1.20240722.0): + - abseil/base/config + - abseil/container/common + - abseil/hash/hash + - abseil/meta/type_traits + - abseil/strings/cord + - abseil/strings/strings + - abseil/xcprivacy + - abseil/container/hash_policy_traits (1.20240722.0): + - abseil/container/common_policy_traits + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/container/hashtable_debug_hooks (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/container/hashtablez_sampler (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/no_destructor + - abseil/base/raw_logging_internal + - abseil/debugging/stacktrace + - abseil/memory/memory + - abseil/profiling/exponential_biased + - abseil/profiling/sample_recorder + - abseil/synchronization/synchronization + - abseil/time/time + - abseil/utility/utility + - abseil/xcprivacy + - abseil/container/inlined_vector (1.20240722.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/container/inlined_vector_internal + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/container/inlined_vector_internal (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/container/compressed_tuple + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/span + - abseil/xcprivacy + - abseil/container/layout (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/debugging/demangle_internal + - abseil/meta/type_traits + - abseil/strings/strings + - abseil/types/span + - abseil/utility/utility + - abseil/xcprivacy + - abseil/container/raw_hash_map (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/throw_delegate + - abseil/container/container_memory + - abseil/container/raw_hash_set + - abseil/xcprivacy + - abseil/container/raw_hash_set (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/endian + - abseil/base/prefetch + - abseil/base/raw_logging_internal + - abseil/container/common + - abseil/container/compressed_tuple + - abseil/container/container_memory + - abseil/container/hash_policy_traits + - abseil/container/hashtable_debug_hooks + - abseil/container/hashtablez_sampler + - abseil/hash/hash + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/utility/utility + - abseil/xcprivacy + - abseil/crc/cpu_detect (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/xcprivacy + - abseil/crc/crc32c (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/prefetch + - abseil/crc/cpu_detect + - abseil/crc/crc_internal + - abseil/crc/non_temporal_memcpy + - abseil/strings/str_format + - abseil/strings/strings + - abseil/xcprivacy + - abseil/crc/crc_cord_state (1.20240722.0): + - abseil/base/config + - abseil/base/no_destructor + - abseil/crc/crc32c + - abseil/numeric/bits + - abseil/xcprivacy + - abseil/crc/crc_internal (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/prefetch + - abseil/base/raw_logging_internal + - abseil/crc/cpu_detect + - abseil/memory/memory + - abseil/numeric/bits + - abseil/xcprivacy + - abseil/crc/non_temporal_arm_intrinsics (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/crc/non_temporal_memcpy (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/crc/non_temporal_arm_intrinsics + - abseil/xcprivacy + - abseil/debugging/bounded_utf8_length_sequence (1.20240722.0): + - abseil/base/config + - abseil/numeric/bits + - abseil/xcprivacy + - abseil/debugging/debugging_internal (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/errno_saver + - abseil/base/raw_logging_internal + - abseil/xcprivacy + - abseil/debugging/decode_rust_punycode (1.20240722.0): + - abseil/base/config + - abseil/base/nullability + - abseil/debugging/bounded_utf8_length_sequence + - abseil/debugging/utf8_for_code_point + - abseil/xcprivacy + - abseil/debugging/demangle_internal (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/nullability + - abseil/debugging/demangle_rust + - abseil/numeric/bits + - abseil/xcprivacy + - abseil/debugging/demangle_rust (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/debugging/decode_rust_punycode + - abseil/xcprivacy + - abseil/debugging/examine_stack (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/debugging/stacktrace + - abseil/debugging/symbolize + - abseil/xcprivacy + - abseil/debugging/stacktrace (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/raw_logging_internal + - abseil/debugging/debugging_internal + - abseil/xcprivacy + - abseil/debugging/symbolize (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/malloc_internal + - abseil/base/raw_logging_internal + - abseil/debugging/debugging_internal + - abseil/debugging/demangle_internal + - abseil/strings/strings + - abseil/xcprivacy + - abseil/debugging/utf8_for_code_point (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/flags/commandlineflag (1.20240722.0): + - abseil/base/config + - abseil/base/fast_type_id + - abseil/flags/commandlineflag_internal + - abseil/strings/strings + - abseil/types/optional + - abseil/xcprivacy + - abseil/flags/commandlineflag_internal (1.20240722.0): + - abseil/base/config + - abseil/base/fast_type_id + - abseil/xcprivacy + - abseil/flags/config (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/flags/path_util + - abseil/flags/program_name + - abseil/strings/strings + - abseil/synchronization/synchronization + - abseil/xcprivacy + - abseil/flags/flag (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/flags/commandlineflag + - abseil/flags/config + - abseil/flags/flag_internal + - abseil/flags/reflection + - abseil/strings/strings + - abseil/xcprivacy + - abseil/flags/flag_internal (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/flags/commandlineflag + - abseil/flags/commandlineflag_internal + - abseil/flags/config + - abseil/flags/marshalling + - abseil/flags/reflection + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/strings/strings + - abseil/synchronization/synchronization + - abseil/utility/utility + - abseil/xcprivacy + - abseil/flags/marshalling (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/numeric/int128 + - abseil/strings/str_format + - abseil/strings/strings + - abseil/types/optional + - abseil/xcprivacy + - abseil/flags/path_util (1.20240722.0): + - abseil/base/config + - abseil/strings/strings + - abseil/xcprivacy + - abseil/flags/private_handle_accessor (1.20240722.0): + - abseil/base/config + - abseil/flags/commandlineflag + - abseil/flags/commandlineflag_internal + - abseil/strings/strings + - abseil/xcprivacy + - abseil/flags/program_name (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/flags/path_util + - abseil/strings/strings + - abseil/synchronization/synchronization + - abseil/xcprivacy + - abseil/flags/reflection (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/no_destructor + - abseil/container/flat_hash_map + - abseil/flags/commandlineflag + - abseil/flags/commandlineflag_internal + - abseil/flags/config + - abseil/flags/private_handle_accessor + - abseil/strings/strings + - abseil/synchronization/synchronization + - abseil/xcprivacy + - abseil/functional/any_invocable (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/utility/utility + - abseil/xcprivacy + - abseil/functional/bind_front (1.20240722.0): + - abseil/base/base_internal + - abseil/container/compressed_tuple + - abseil/meta/type_traits + - abseil/utility/utility + - abseil/xcprivacy + - abseil/functional/function_ref (1.20240722.0): + - abseil/base/base_internal + - abseil/base/core_headers + - abseil/functional/any_invocable + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/hash/city (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/xcprivacy + - abseil/hash/hash (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/container/fixed_array + - abseil/functional/function_ref + - abseil/hash/city + - abseil/hash/low_level_hash + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/types/optional + - abseil/types/variant + - abseil/utility/utility + - abseil/xcprivacy + - abseil/hash/low_level_hash (1.20240722.0): + - abseil/base/config + - abseil/base/endian + - abseil/base/prefetch + - abseil/numeric/int128 + - abseil/xcprivacy + - abseil/log/absl_check (1.20240722.0): + - abseil/log/internal/check_impl + - abseil/xcprivacy + - abseil/log/absl_log (1.20240722.0): + - abseil/log/internal/log_impl + - abseil/xcprivacy + - abseil/log/absl_vlog_is_on (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/log/internal/vlog_config + - abseil/strings/strings + - abseil/xcprivacy + - abseil/log/check (1.20240722.0): + - abseil/log/internal/check_impl + - abseil/log/internal/check_op + - abseil/log/internal/conditions + - abseil/log/internal/log_message + - abseil/log/internal/strip + - abseil/xcprivacy + - abseil/log/globals (1.20240722.0): + - abseil/base/atomic_hook + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/base/raw_logging_internal + - abseil/hash/hash + - abseil/log/internal/vlog_config + - abseil/strings/strings + - abseil/xcprivacy + - abseil/log/internal/append_truncated (1.20240722.0): + - abseil/base/config + - abseil/strings/strings + - abseil/types/span + - abseil/xcprivacy + - abseil/log/internal/check_impl (1.20240722.0): + - abseil/base/core_headers + - abseil/log/internal/check_op + - abseil/log/internal/conditions + - abseil/log/internal/log_message + - abseil/log/internal/strip + - abseil/xcprivacy + - abseil/log/internal/check_op (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/log/internal/nullguard + - abseil/log/internal/nullstream + - abseil/log/internal/strip + - abseil/strings/strings + - abseil/xcprivacy + - abseil/log/internal/conditions (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/log/internal/voidify + - abseil/xcprivacy + - abseil/log/internal/config (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/log/internal/fnmatch (1.20240722.0): + - abseil/base/config + - abseil/strings/strings + - abseil/xcprivacy + - abseil/log/internal/format (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/log/internal/append_truncated + - abseil/log/internal/config + - abseil/log/internal/globals + - abseil/strings/str_format + - abseil/strings/strings + - abseil/time/time + - abseil/types/span + - abseil/xcprivacy + - abseil/log/internal/globals (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/base/raw_logging_internal + - abseil/strings/strings + - abseil/time/time + - abseil/xcprivacy + - abseil/log/internal/log_impl (1.20240722.0): + - abseil/log/absl_vlog_is_on + - abseil/log/internal/conditions + - abseil/log/internal/log_message + - abseil/log/internal/strip + - abseil/xcprivacy + - abseil/log/internal/log_message (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/errno_saver + - abseil/base/log_severity + - abseil/base/raw_logging_internal + - abseil/base/strerror + - abseil/container/inlined_vector + - abseil/debugging/examine_stack + - abseil/log/globals + - abseil/log/internal/append_truncated + - abseil/log/internal/format + - abseil/log/internal/globals + - abseil/log/internal/log_sink_set + - abseil/log/internal/nullguard + - abseil/log/internal/proto + - abseil/log/log_entry + - abseil/log/log_sink + - abseil/log/log_sink_registry + - abseil/memory/memory + - abseil/strings/strings + - abseil/time/time + - abseil/types/span + - abseil/xcprivacy + - abseil/log/internal/log_sink_set (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/base/no_destructor + - abseil/base/raw_logging_internal + - abseil/cleanup/cleanup + - abseil/log/globals + - abseil/log/internal/config + - abseil/log/internal/globals + - abseil/log/log_entry + - abseil/log/log_sink + - abseil/strings/strings + - abseil/synchronization/synchronization + - abseil/types/span + - abseil/xcprivacy + - abseil/log/internal/nullguard (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/log/internal/nullstream (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/strings/strings + - abseil/xcprivacy + - abseil/log/internal/proto (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/strings/strings + - abseil/types/span + - abseil/xcprivacy + - abseil/log/internal/strip (1.20240722.0): + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/log/internal/log_message + - abseil/log/internal/nullstream + - abseil/xcprivacy + - abseil/log/internal/vlog_config (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/no_destructor + - abseil/log/internal/fnmatch + - abseil/memory/memory + - abseil/strings/strings + - abseil/synchronization/synchronization + - abseil/types/optional + - abseil/xcprivacy + - abseil/log/internal/voidify (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/log/log (1.20240722.0): + - abseil/log/internal/log_impl + - abseil/log/vlog_is_on + - abseil/xcprivacy + - abseil/log/log_entry (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/log_severity + - abseil/log/internal/config + - abseil/strings/strings + - abseil/time/time + - abseil/types/span + - abseil/xcprivacy + - abseil/log/log_sink (1.20240722.0): + - abseil/base/config + - abseil/log/log_entry + - abseil/xcprivacy + - abseil/log/log_sink_registry (1.20240722.0): + - abseil/base/config + - abseil/log/internal/log_sink_set + - abseil/log/log_sink + - abseil/xcprivacy + - abseil/log/vlog_is_on (1.20240722.0): + - abseil/log/absl_vlog_is_on + - abseil/xcprivacy + - abseil/memory (1.20240722.0): + - abseil/memory/memory (= 1.20240722.0) + - abseil/memory/memory (1.20240722.0): + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/meta (1.20240722.0): + - abseil/meta/type_traits (= 1.20240722.0) + - abseil/meta/type_traits (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/numeric/bits (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/numeric/int128 (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/numeric/bits + - abseil/types/compare + - abseil/xcprivacy + - abseil/numeric/representation (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/profiling/exponential_biased (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/xcprivacy + - abseil/profiling/sample_recorder (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/synchronization/synchronization + - abseil/time/time + - abseil/xcprivacy + - abseil/random/bit_gen_ref (1.20240722.0): + - abseil/base/core_headers + - abseil/base/fast_type_id + - abseil/meta/type_traits + - abseil/random/internal/distribution_caller + - abseil/random/internal/fast_uniform_bits + - abseil/random/random + - abseil/xcprivacy + - abseil/random/distributions (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/random/internal/distribution_caller + - abseil/random/internal/fast_uniform_bits + - abseil/random/internal/fastmath + - abseil/random/internal/generate_real + - abseil/random/internal/iostream_state_saver + - abseil/random/internal/traits + - abseil/random/internal/uniform_helper + - abseil/random/internal/wide_multiply + - abseil/strings/strings + - abseil/xcprivacy + - abseil/random/internal/distribution_caller (1.20240722.0): + - abseil/base/config + - abseil/base/fast_type_id + - abseil/utility/utility + - abseil/xcprivacy + - abseil/random/internal/fast_uniform_bits (1.20240722.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/random/internal/traits + - abseil/xcprivacy + - abseil/random/internal/fastmath (1.20240722.0): + - abseil/numeric/bits + - abseil/xcprivacy + - abseil/random/internal/generate_real (1.20240722.0): + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/random/internal/fastmath + - abseil/random/internal/traits + - abseil/xcprivacy + - abseil/random/internal/iostream_state_saver (1.20240722.0): + - abseil/meta/type_traits + - abseil/numeric/int128 + - abseil/xcprivacy + - abseil/random/internal/nonsecure_base (1.20240722.0): + - abseil/base/core_headers + - abseil/container/inlined_vector + - abseil/meta/type_traits + - abseil/random/internal/pool_urbg + - abseil/random/internal/salted_seed_seq + - abseil/random/internal/seed_material + - abseil/types/span + - abseil/xcprivacy + - abseil/random/internal/pcg_engine (1.20240722.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/random/internal/fastmath + - abseil/random/internal/iostream_state_saver + - abseil/xcprivacy + - abseil/random/internal/platform (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/random/internal/pool_urbg (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/random/internal/randen + - abseil/random/internal/seed_material + - abseil/random/internal/traits + - abseil/random/seed_gen_exception + - abseil/types/span + - abseil/xcprivacy + - abseil/random/internal/randen (1.20240722.0): + - abseil/base/raw_logging_internal + - abseil/random/internal/platform + - abseil/random/internal/randen_hwaes + - abseil/random/internal/randen_slow + - abseil/xcprivacy + - abseil/random/internal/randen_engine (1.20240722.0): + - abseil/base/endian + - abseil/meta/type_traits + - abseil/random/internal/iostream_state_saver + - abseil/random/internal/randen + - abseil/xcprivacy + - abseil/random/internal/randen_hwaes (1.20240722.0): + - abseil/base/config + - abseil/random/internal/platform + - abseil/random/internal/randen_hwaes_impl + - abseil/xcprivacy + - abseil/random/internal/randen_hwaes_impl (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/numeric/int128 + - abseil/random/internal/platform + - abseil/xcprivacy + - abseil/random/internal/randen_slow (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/numeric/int128 + - abseil/random/internal/platform + - abseil/xcprivacy + - abseil/random/internal/salted_seed_seq (1.20240722.0): + - abseil/container/inlined_vector + - abseil/meta/type_traits + - abseil/random/internal/seed_material + - abseil/types/optional + - abseil/types/span + - abseil/xcprivacy + - abseil/random/internal/seed_material (1.20240722.0): + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/raw_logging_internal + - abseil/random/internal/fast_uniform_bits + - abseil/strings/strings + - abseil/types/optional + - abseil/types/span + - abseil/xcprivacy + - abseil/random/internal/traits (1.20240722.0): + - abseil/base/config + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/xcprivacy + - abseil/random/internal/uniform_helper (1.20240722.0): + - abseil/base/config + - abseil/meta/type_traits + - abseil/random/internal/traits + - abseil/xcprivacy + - abseil/random/internal/wide_multiply (1.20240722.0): + - abseil/base/config + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/random/internal/traits + - abseil/xcprivacy + - abseil/random/random (1.20240722.0): + - abseil/random/distributions + - abseil/random/internal/nonsecure_base + - abseil/random/internal/pcg_engine + - abseil/random/internal/pool_urbg + - abseil/random/internal/randen_engine + - abseil/random/seed_sequences + - abseil/xcprivacy + - abseil/random/seed_gen_exception (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/random/seed_sequences (1.20240722.0): + - abseil/base/config + - abseil/base/nullability + - abseil/random/internal/pool_urbg + - abseil/random/internal/salted_seed_seq + - abseil/random/internal/seed_material + - abseil/random/seed_gen_exception + - abseil/strings/string_view + - abseil/types/span + - abseil/xcprivacy + - abseil/status/status (1.20240722.0): + - abseil/base/atomic_hook + - abseil/base/config + - abseil/base/core_headers + - abseil/base/no_destructor + - abseil/base/nullability + - abseil/base/raw_logging_internal + - abseil/base/strerror + - abseil/container/inlined_vector + - abseil/debugging/stacktrace + - abseil/debugging/symbolize + - abseil/functional/function_ref + - abseil/memory/memory + - abseil/strings/cord + - abseil/strings/str_format + - abseil/strings/strings + - abseil/types/optional + - abseil/types/span + - abseil/xcprivacy + - abseil/status/statusor (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/nullability + - abseil/base/raw_logging_internal + - abseil/meta/type_traits + - abseil/status/status + - abseil/strings/has_ostream_operator + - abseil/strings/str_format + - abseil/strings/strings + - abseil/types/variant + - abseil/utility/utility + - abseil/xcprivacy + - abseil/strings/charset (1.20240722.0): + - abseil/base/core_headers + - abseil/strings/string_view + - abseil/xcprivacy + - abseil/strings/cord (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/nullability + - abseil/base/raw_logging_internal + - abseil/container/inlined_vector + - abseil/crc/crc32c + - abseil/crc/crc_cord_state + - abseil/functional/function_ref + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/strings/cord_internal + - abseil/strings/cordz_functions + - abseil/strings/cordz_info + - abseil/strings/cordz_statistics + - abseil/strings/cordz_update_scope + - abseil/strings/cordz_update_tracker + - abseil/strings/internal + - abseil/strings/strings + - abseil/types/compare + - abseil/types/optional + - abseil/types/span + - abseil/xcprivacy + - abseil/strings/cord_internal (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/base/throw_delegate + - abseil/container/compressed_tuple + - abseil/container/container_memory + - abseil/container/inlined_vector + - abseil/container/layout + - abseil/crc/crc_cord_state + - abseil/functional/function_ref + - abseil/meta/type_traits + - abseil/strings/strings + - abseil/types/span + - abseil/xcprivacy + - abseil/strings/cordz_functions (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/profiling/exponential_biased + - abseil/xcprivacy + - abseil/strings/cordz_handle (1.20240722.0): + - abseil/base/config + - abseil/base/no_destructor + - abseil/base/raw_logging_internal + - abseil/synchronization/synchronization + - abseil/xcprivacy + - abseil/strings/cordz_info (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/container/inlined_vector + - abseil/debugging/stacktrace + - abseil/strings/cord_internal + - abseil/strings/cordz_functions + - abseil/strings/cordz_handle + - abseil/strings/cordz_statistics + - abseil/strings/cordz_update_tracker + - abseil/synchronization/synchronization + - abseil/time/time + - abseil/types/span + - abseil/xcprivacy + - abseil/strings/cordz_statistics (1.20240722.0): + - abseil/base/config + - abseil/strings/cordz_update_tracker + - abseil/xcprivacy + - abseil/strings/cordz_update_scope (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/strings/cord_internal + - abseil/strings/cordz_info + - abseil/strings/cordz_update_tracker + - abseil/xcprivacy + - abseil/strings/cordz_update_tracker (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/strings/has_ostream_operator (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/strings/internal (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/raw_logging_internal + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/strings/str_format (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/nullability + - abseil/strings/str_format_internal + - abseil/strings/string_view + - abseil/types/span + - abseil/xcprivacy + - abseil/strings/str_format_internal (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/container/fixed_array + - abseil/container/inlined_vector + - abseil/functional/function_ref + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/numeric/representation + - abseil/strings/strings + - abseil/types/optional + - abseil/types/span + - abseil/utility/utility + - abseil/xcprivacy + - abseil/strings/string_view (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/nullability + - abseil/base/throw_delegate + - abseil/xcprivacy + - abseil/strings/strings (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/endian + - abseil/base/nullability + - abseil/base/raw_logging_internal + - abseil/base/throw_delegate + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/numeric/bits + - abseil/numeric/int128 + - abseil/strings/charset + - abseil/strings/internal + - abseil/strings/string_view + - abseil/xcprivacy + - abseil/synchronization/graphcycles_internal (1.20240722.0): + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/malloc_internal + - abseil/base/raw_logging_internal + - abseil/xcprivacy + - abseil/synchronization/kernel_timeout_internal (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/time/time + - abseil/xcprivacy + - abseil/synchronization/synchronization (1.20240722.0): + - abseil/base/atomic_hook + - abseil/base/base + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/dynamic_annotations + - abseil/base/malloc_internal + - abseil/base/raw_logging_internal + - abseil/debugging/stacktrace + - abseil/debugging/symbolize + - abseil/synchronization/graphcycles_internal + - abseil/synchronization/kernel_timeout_internal + - abseil/time/time + - abseil/xcprivacy + - abseil/time (1.20240722.0): + - abseil/time/internal (= 1.20240722.0) + - abseil/time/time (= 1.20240722.0) + - abseil/time/internal (1.20240722.0): + - abseil/time/internal/cctz (= 1.20240722.0) + - abseil/time/internal/cctz (1.20240722.0): + - abseil/time/internal/cctz/civil_time (= 1.20240722.0) + - abseil/time/internal/cctz/time_zone (= 1.20240722.0) + - abseil/time/internal/cctz/civil_time (1.20240722.0): + - abseil/base/config + - abseil/xcprivacy + - abseil/time/internal/cctz/time_zone (1.20240722.0): + - abseil/base/config + - abseil/time/internal/cctz/civil_time + - abseil/xcprivacy + - abseil/time/time (1.20240722.0): + - abseil/base/base + - abseil/base/config + - abseil/base/core_headers + - abseil/base/raw_logging_internal + - abseil/numeric/int128 + - abseil/strings/strings + - abseil/time/internal/cctz/civil_time + - abseil/time/internal/cctz/time_zone + - abseil/types/optional + - abseil/xcprivacy + - abseil/types (1.20240722.0): + - abseil/types/any (= 1.20240722.0) + - abseil/types/bad_any_cast (= 1.20240722.0) + - abseil/types/bad_any_cast_impl (= 1.20240722.0) + - abseil/types/bad_optional_access (= 1.20240722.0) + - abseil/types/bad_variant_access (= 1.20240722.0) + - abseil/types/compare (= 1.20240722.0) + - abseil/types/optional (= 1.20240722.0) + - abseil/types/span (= 1.20240722.0) + - abseil/types/variant (= 1.20240722.0) + - abseil/types/any (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/base/fast_type_id + - abseil/meta/type_traits + - abseil/types/bad_any_cast + - abseil/utility/utility + - abseil/xcprivacy + - abseil/types/bad_any_cast (1.20240722.0): + - abseil/base/config + - abseil/types/bad_any_cast_impl + - abseil/xcprivacy + - abseil/types/bad_any_cast_impl (1.20240722.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/xcprivacy + - abseil/types/bad_optional_access (1.20240722.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/xcprivacy + - abseil/types/bad_variant_access (1.20240722.0): + - abseil/base/config + - abseil/base/raw_logging_internal + - abseil/xcprivacy + - abseil/types/compare (1.20240722.0): + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/types/optional (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/base/nullability + - abseil/memory/memory + - abseil/meta/type_traits + - abseil/types/bad_optional_access + - abseil/utility/utility + - abseil/xcprivacy + - abseil/types/span (1.20240722.0): + - abseil/algorithm/algorithm + - abseil/base/core_headers + - abseil/base/nullability + - abseil/base/throw_delegate + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/types/variant (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/base/core_headers + - abseil/meta/type_traits + - abseil/types/bad_variant_access + - abseil/utility/utility + - abseil/xcprivacy + - abseil/utility/utility (1.20240722.0): + - abseil/base/base_internal + - abseil/base/config + - abseil/meta/type_traits + - abseil/xcprivacy + - abseil/xcprivacy (1.20240722.0) + - awesome_notifications (0.10.0): + - Flutter + - IosAwnCore (~> 0.10.0) + - BoringSSL-GRPC (0.0.37): + - BoringSSL-GRPC/Implementation (= 0.0.37) + - BoringSSL-GRPC/Interface (= 0.0.37) + - BoringSSL-GRPC/Implementation (0.0.37): + - BoringSSL-GRPC/Interface (= 0.0.37) + - BoringSSL-GRPC/Interface (0.0.37) + - cloud_firestore (5.6.8): + - Firebase/Firestore (= 11.10.0) + - firebase_core + - Flutter + - Firebase/Auth (11.10.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 11.10.0) + - Firebase/CoreOnly (11.10.0): + - FirebaseCore (~> 11.10.0) + - Firebase/Firestore (11.10.0): + - Firebase/CoreOnly + - FirebaseFirestore (~> 11.10.0) + - Firebase/Messaging (11.10.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 11.10.0) + - Firebase/Storage (11.10.0): + - Firebase/CoreOnly + - FirebaseStorage (~> 11.10.0) + - firebase_auth (5.5.4): + - Firebase/Auth (= 11.10.0) + - firebase_core + - Flutter + - firebase_core (3.13.1): + - Firebase/CoreOnly (= 11.10.0) + - Flutter + - firebase_messaging (15.2.6): + - Firebase/Messaging (= 11.10.0) + - firebase_core + - Flutter + - firebase_storage (12.4.6): + - Firebase/Storage (= 11.10.0) + - firebase_core + - Flutter + - FirebaseAppCheckInterop (11.13.0) + - FirebaseAuth (11.10.0): + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseAuthInterop (~> 11.0) + - FirebaseCore (~> 11.10.0) + - FirebaseCoreExtension (~> 11.10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.0) + - GoogleUtilities/Environment (~> 8.0) + - GTMSessionFetcher/Core (< 5.0, >= 3.4) + - RecaptchaInterop (~> 101.0) + - FirebaseAuthInterop (11.13.0) + - FirebaseCore (11.10.0): + - FirebaseCoreInternal (~> 11.10.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Logger (~> 8.0) + - FirebaseCoreExtension (11.10.0): + - FirebaseCore (~> 11.10.0) + - FirebaseCoreInternal (11.10.0): + - "GoogleUtilities/NSData+zlib (~> 8.0)" + - FirebaseFirestore (11.10.0): + - FirebaseCore (~> 11.10.0) + - FirebaseCoreExtension (~> 11.10.0) + - FirebaseFirestoreInternal (= 11.10.0) + - FirebaseSharedSwift (~> 11.0) + - FirebaseFirestoreInternal (11.10.0): + - abseil/algorithm (~> 1.20240722.0) + - abseil/base (~> 1.20240722.0) + - abseil/container/flat_hash_map (~> 1.20240722.0) + - abseil/memory (~> 1.20240722.0) + - abseil/meta (~> 1.20240722.0) + - abseil/strings/strings (~> 1.20240722.0) + - abseil/time (~> 1.20240722.0) + - abseil/types (~> 1.20240722.0) + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseCore (~> 11.10.0) + - "gRPC-C++ (~> 1.69.0)" + - gRPC-Core (~> 1.69.0) + - leveldb-library (~> 1.22) + - nanopb (~> 3.30910.0) + - FirebaseInstallations (11.10.0): + - FirebaseCore (~> 11.10.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) + - FirebaseMessaging (11.10.0): + - FirebaseCore (~> 11.10.0) + - FirebaseInstallations (~> 11.0) + - GoogleDataTransport (~> 10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Reachability (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - nanopb (~> 3.30910.0) + - FirebaseSharedSwift (11.13.0) + - FirebaseStorage (11.10.0): + - FirebaseAppCheckInterop (~> 11.0) + - FirebaseAuthInterop (~> 11.0) + - FirebaseCore (~> 11.10.0) + - FirebaseCoreExtension (~> 11.10.0) + - GoogleUtilities/Environment (~> 8.0) + - GTMSessionFetcher/Core (< 5.0, >= 3.4) + - Flutter (1.0.0) + - flutter_sslcommerz (2.3.9): + - Flutter + - SSLCommerzSDK + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleUtilities/AppDelegateSwizzler (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.1.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.1.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/Reachability (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - "gRPC-C++ (1.69.0)": + - "gRPC-C++/Implementation (= 1.69.0)" + - "gRPC-C++/Interface (= 1.69.0)" + - "gRPC-C++/Implementation (1.69.0)": + - abseil/algorithm/container (~> 1.20240722.0) + - abseil/base/base (~> 1.20240722.0) + - abseil/base/config (~> 1.20240722.0) + - abseil/base/core_headers (~> 1.20240722.0) + - abseil/base/log_severity (~> 1.20240722.0) + - abseil/base/no_destructor (~> 1.20240722.0) + - abseil/cleanup/cleanup (~> 1.20240722.0) + - abseil/container/flat_hash_map (~> 1.20240722.0) + - abseil/container/flat_hash_set (~> 1.20240722.0) + - abseil/container/inlined_vector (~> 1.20240722.0) + - abseil/flags/flag (~> 1.20240722.0) + - abseil/flags/marshalling (~> 1.20240722.0) + - abseil/functional/any_invocable (~> 1.20240722.0) + - abseil/functional/bind_front (~> 1.20240722.0) + - abseil/functional/function_ref (~> 1.20240722.0) + - abseil/hash/hash (~> 1.20240722.0) + - abseil/log/absl_check (~> 1.20240722.0) + - abseil/log/absl_log (~> 1.20240722.0) + - abseil/log/check (~> 1.20240722.0) + - abseil/log/globals (~> 1.20240722.0) + - abseil/log/log (~> 1.20240722.0) + - abseil/memory/memory (~> 1.20240722.0) + - abseil/meta/type_traits (~> 1.20240722.0) + - abseil/numeric/bits (~> 1.20240722.0) + - abseil/random/bit_gen_ref (~> 1.20240722.0) + - abseil/random/distributions (~> 1.20240722.0) + - abseil/random/random (~> 1.20240722.0) + - abseil/status/status (~> 1.20240722.0) + - abseil/status/statusor (~> 1.20240722.0) + - abseil/strings/cord (~> 1.20240722.0) + - abseil/strings/str_format (~> 1.20240722.0) + - abseil/strings/strings (~> 1.20240722.0) + - abseil/synchronization/synchronization (~> 1.20240722.0) + - abseil/time/time (~> 1.20240722.0) + - abseil/types/optional (~> 1.20240722.0) + - abseil/types/span (~> 1.20240722.0) + - abseil/types/variant (~> 1.20240722.0) + - abseil/utility/utility (~> 1.20240722.0) + - "gRPC-C++/Interface (= 1.69.0)" + - "gRPC-C++/Privacy (= 1.69.0)" + - gRPC-Core (= 1.69.0) + - "gRPC-C++/Interface (1.69.0)" + - "gRPC-C++/Privacy (1.69.0)" + - gRPC-Core (1.69.0): + - gRPC-Core/Implementation (= 1.69.0) + - gRPC-Core/Interface (= 1.69.0) + - gRPC-Core/Implementation (1.69.0): + - abseil/algorithm/container (~> 1.20240722.0) + - abseil/base/base (~> 1.20240722.0) + - abseil/base/config (~> 1.20240722.0) + - abseil/base/core_headers (~> 1.20240722.0) + - abseil/base/log_severity (~> 1.20240722.0) + - abseil/base/no_destructor (~> 1.20240722.0) + - abseil/cleanup/cleanup (~> 1.20240722.0) + - abseil/container/flat_hash_map (~> 1.20240722.0) + - abseil/container/flat_hash_set (~> 1.20240722.0) + - abseil/container/inlined_vector (~> 1.20240722.0) + - abseil/flags/flag (~> 1.20240722.0) + - abseil/flags/marshalling (~> 1.20240722.0) + - abseil/functional/any_invocable (~> 1.20240722.0) + - abseil/functional/bind_front (~> 1.20240722.0) + - abseil/functional/function_ref (~> 1.20240722.0) + - abseil/hash/hash (~> 1.20240722.0) + - abseil/log/check (~> 1.20240722.0) + - abseil/log/globals (~> 1.20240722.0) + - abseil/log/log (~> 1.20240722.0) + - abseil/memory/memory (~> 1.20240722.0) + - abseil/meta/type_traits (~> 1.20240722.0) + - abseil/numeric/bits (~> 1.20240722.0) + - abseil/random/bit_gen_ref (~> 1.20240722.0) + - abseil/random/distributions (~> 1.20240722.0) + - abseil/random/random (~> 1.20240722.0) + - abseil/status/status (~> 1.20240722.0) + - abseil/status/statusor (~> 1.20240722.0) + - abseil/strings/cord (~> 1.20240722.0) + - abseil/strings/str_format (~> 1.20240722.0) + - abseil/strings/strings (~> 1.20240722.0) + - abseil/synchronization/synchronization (~> 1.20240722.0) + - abseil/time/time (~> 1.20240722.0) + - abseil/types/optional (~> 1.20240722.0) + - abseil/types/span (~> 1.20240722.0) + - abseil/types/variant (~> 1.20240722.0) + - abseil/utility/utility (~> 1.20240722.0) + - BoringSSL-GRPC (= 0.0.37) + - gRPC-Core/Interface (= 1.69.0) + - gRPC-Core/Privacy (= 1.69.0) + - gRPC-Core/Interface (1.69.0) + - gRPC-Core/Privacy (1.69.0) + - GTMSessionFetcher/Core (4.5.0) + - image_picker_ios (0.0.1): + - Flutter + - IosAwnCore (0.10.0) + - leveldb-library (1.22.6) + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - open_file_ios (0.0.1): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - printing (1.0.0): + - Flutter + - PromisesObjC (2.4.0) + - RecaptchaInterop (101.0.0) + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - SSLCommerzSDK (2.0.5) + +DEPENDENCIES: + - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`) + - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) + - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) + - Flutter (from `Flutter`) + - flutter_sslcommerz (from `.symlinks/plugins/flutter_sslcommerz/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - printing (from `.symlinks/plugins/printing/ios`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + +SPEC REPOS: + trunk: + - abseil + - BoringSSL-GRPC + - Firebase + - FirebaseAppCheckInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - FirebaseFirestore + - FirebaseFirestoreInternal + - FirebaseInstallations + - FirebaseMessaging + - FirebaseSharedSwift + - FirebaseStorage + - GoogleDataTransport + - GoogleUtilities + - "gRPC-C++" + - gRPC-Core + - GTMSessionFetcher + - IosAwnCore + - leveldb-library + - nanopb + - PromisesObjC + - RecaptchaInterop + - SSLCommerzSDK + +EXTERNAL SOURCES: + awesome_notifications: + :path: ".symlinks/plugins/awesome_notifications/ios" + cloud_firestore: + :path: ".symlinks/plugins/cloud_firestore/ios" + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" + firebase_storage: + :path: ".symlinks/plugins/firebase_storage/ios" + Flutter: + :path: Flutter + flutter_sslcommerz: + :path: ".symlinks/plugins/flutter_sslcommerz/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + open_file_ios: + :path: ".symlinks/plugins/open_file_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + printing: + :path: ".symlinks/plugins/printing/ios" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + +SPEC CHECKSUMS: + abseil: a05cc83bf02079535e17169a73c5be5ba47f714b + awesome_notifications: dd5518ff1c80be03d4f1c40f04da9d9cc2a37af5 + BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508 + cloud_firestore: 418c4911a2e4f1c38ac6dbb838ef19cdc32a515f + Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 + firebase_auth: 519455ee22dea9cb8b4e05d7821f93f77db88882 + firebase_core: 3c2f323cae65c97a636a05a23b17730ef93df2cf + firebase_messaging: 456e01ff29a451c90097d0b45925551d5be0c143 + firebase_storage: a53140c25885d8de77fecf98cbaa0d9fff501598 + FirebaseAppCheckInterop: 72066489c209823649a997132bcd9269bc33a4bb + FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32 + FirebaseAuthInterop: 4fa327ec3c551a80a6929561f83af80b1dd44937 + FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 + FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac + FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 + FirebaseFirestore: 3f1488ff7739cb3c5d10e572bc4e9fcd8e8cb4ac + FirebaseFirestoreInternal: 97a2bb5f16951c77753c860d3519379702ab6f8a + FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 + FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 + FirebaseSharedSwift: aca73668bc95e8efccb618e0167eab05d19d3a75 + FirebaseStorage: e83d1b9c8a5318d46ccfb2955f0d98095e0bf598 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_sslcommerz: e8a42bb445dbf8a4e965c3894188411765adc2f5 + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 + "gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8 + gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330 + GTMSessionFetcher: fc75fc972958dceedee61cb662ae1da7a83a91cf + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + IosAwnCore: 653786a911089012092ce831f2945cd339855a89 + leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + open_file_ios: 461db5853723763573e140de3193656f91990d9e + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 + printing: 233e1b73bd1f4a05615548e9b5a324c98588640b + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + SSLCommerzSDK: 9997682baa0a9ae4a69fb9a2be62cd11be6fe848 + +PODFILE CHECKSUM: fdcd7799e3f3830110a9695ac577d61ab285fd7a + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 2c73a7c..a64d769 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -1,759 +1,759 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 101DCD782411894879CAC68D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = CCBACA3BA2CEEA95143317EB /* GoogleService-Info.plist */; }; - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 1BCE16E404DEC4650F6FF07E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D94121D01127A5151701C383 /* Pods_RunnerTests.framework */; }; - 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 */; }; - 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 */; }; - DCCDA6243C7F070C8B313DB1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0093643D10FBC9568AB97391 /* Pods_Runner.framework */; }; -/* 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 */ - 0093643D10FBC9568AB97391 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 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 = ""; }; - 4F24C13F44106487BBBB8EDA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - 556F2677978D870A13B24F2A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 6C3F556576B64944FCDBB326 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; 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 = ""; }; - 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 = ""; }; - C72C05283BCD4D540D267EBB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - CCBACA3BA2CEEA95143317EB /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; - D94121D01127A5151701C383 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F3F8E26D685BBF333A1B190A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - FDD3B7DC0863775B1A46F566 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 887025B4018ECEB60EDFF5DF /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1BCE16E404DEC4650F6FF07E /* Pods_RunnerTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - DCCDA6243C7F070C8B313DB1 /* Pods_Runner.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 44D84A5C8C41A58FB9B4B585 /* Pods */ = { - isa = PBXGroup; - children = ( - 556F2677978D870A13B24F2A /* Pods-Runner.debug.xcconfig */, - FDD3B7DC0863775B1A46F566 /* Pods-Runner.release.xcconfig */, - F3F8E26D685BBF333A1B190A /* Pods-Runner.profile.xcconfig */, - 4F24C13F44106487BBBB8EDA /* Pods-RunnerTests.debug.xcconfig */, - C72C05283BCD4D540D267EBB /* Pods-RunnerTests.release.xcconfig */, - 6C3F556576B64944FCDBB326 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; - 46462F716B8667E952214ECC /* Frameworks */ = { - isa = PBXGroup; - children = ( - 0093643D10FBC9568AB97391 /* Pods_Runner.framework */, - D94121D01127A5151701C383 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 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 */, - CCBACA3BA2CEEA95143317EB /* GoogleService-Info.plist */, - 44D84A5C8C41A58FB9B4B585 /* Pods */, - 46462F716B8667E952214ECC /* Frameworks */, - ); - 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 */, - 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 = ( - D4C038BAD9A9694851AC6FFE /* [CP] Check Pods Manifest.lock */, - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - 887025B4018ECEB60EDFF5DF /* Frameworks */, - ); - 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 = ( - 8E2ADE6529029F1B949B5DA3 /* [CP] Check Pods Manifest.lock */, - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 3A9E69ECF04A3047EBBC64A6 /* [CP] Embed Pods Frameworks */, - 9CE03589FF246A807240E2B2 /* [CP] Copy Pods Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - 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; - 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 */, - 101DCD782411894879CAC68D /* GoogleService-Info.plist in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3A9E69ECF04A3047EBBC64A6 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 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"; - }; - 8E2ADE6529029F1B949B5DA3 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 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"; - }; - 9CE03589FF246A807240E2B2 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - D4C038BAD9A9694851AC6FFE /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* 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 */, - ); - 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 = 12.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 = { - APPLICATION_EXTENSION_API_ONLY = NO; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - BUILD_LIBRARY_FOR_DISTRIBUTION = NO; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 52R585FMZF; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu; - 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; - baseConfigurationReference = 4F24C13F44106487BBBB8EDA /* Pods-RunnerTests.debug.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.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; - baseConfigurationReference = C72C05283BCD4D540D267EBB /* Pods-RunnerTests.release.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.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; - baseConfigurationReference = 6C3F556576B64944FCDBB326 /* Pods-RunnerTests.profile.xcconfig */; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.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 = 12.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 = 12.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 = { - APPLICATION_EXTENSION_API_ONLY = NO; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - BUILD_LIBRARY_FOR_DISTRIBUTION = NO; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 52R585FMZF; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu; - 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 = { - APPLICATION_EXTENSION_API_ONLY = NO; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - BUILD_LIBRARY_FOR_DISTRIBUTION = NO; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 52R585FMZF; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu; - 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 */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 101DCD782411894879CAC68D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = CCBACA3BA2CEEA95143317EB /* GoogleService-Info.plist */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 1BCE16E404DEC4650F6FF07E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D94121D01127A5151701C383 /* Pods_RunnerTests.framework */; }; + 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 */; }; + 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 */; }; + DCCDA6243C7F070C8B313DB1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0093643D10FBC9568AB97391 /* Pods_Runner.framework */; }; +/* 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 */ + 0093643D10FBC9568AB97391 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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 = ""; }; + 4F24C13F44106487BBBB8EDA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 556F2677978D870A13B24F2A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 6C3F556576B64944FCDBB326 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; 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 = ""; }; + 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 = ""; }; + C72C05283BCD4D540D267EBB /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + CCBACA3BA2CEEA95143317EB /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; + D94121D01127A5151701C383 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + F3F8E26D685BBF333A1B190A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + FDD3B7DC0863775B1A46F566 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 887025B4018ECEB60EDFF5DF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1BCE16E404DEC4650F6FF07E /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DCCDA6243C7F070C8B313DB1 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 44D84A5C8C41A58FB9B4B585 /* Pods */ = { + isa = PBXGroup; + children = ( + 556F2677978D870A13B24F2A /* Pods-Runner.debug.xcconfig */, + FDD3B7DC0863775B1A46F566 /* Pods-Runner.release.xcconfig */, + F3F8E26D685BBF333A1B190A /* Pods-Runner.profile.xcconfig */, + 4F24C13F44106487BBBB8EDA /* Pods-RunnerTests.debug.xcconfig */, + C72C05283BCD4D540D267EBB /* Pods-RunnerTests.release.xcconfig */, + 6C3F556576B64944FCDBB326 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 46462F716B8667E952214ECC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0093643D10FBC9568AB97391 /* Pods_Runner.framework */, + D94121D01127A5151701C383 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 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 */, + CCBACA3BA2CEEA95143317EB /* GoogleService-Info.plist */, + 44D84A5C8C41A58FB9B4B585 /* Pods */, + 46462F716B8667E952214ECC /* Frameworks */, + ); + 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 */, + 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 = ( + D4C038BAD9A9694851AC6FFE /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 887025B4018ECEB60EDFF5DF /* Frameworks */, + ); + 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 = ( + 8E2ADE6529029F1B949B5DA3 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 3A9E69ECF04A3047EBBC64A6 /* [CP] Embed Pods Frameworks */, + 9CE03589FF246A807240E2B2 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + 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; + 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 */, + 101DCD782411894879CAC68D /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3A9E69ECF04A3047EBBC64A6 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + 8E2ADE6529029F1B949B5DA3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + 9CE03589FF246A807240E2B2 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + D4C038BAD9A9694851AC6FFE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* 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 */, + ); + 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 = 12.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 = { + APPLICATION_EXTENSION_API_ONLY = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 52R585FMZF; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu; + 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; + baseConfigurationReference = 4F24C13F44106487BBBB8EDA /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.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; + baseConfigurationReference = C72C05283BCD4D540D267EBB /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.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; + baseConfigurationReference = 6C3F556576B64944FCDBB326 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.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 = 12.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 = 12.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 = { + APPLICATION_EXTENSION_API_ONLY = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 52R585FMZF; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu; + 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 = { + APPLICATION_EXTENSION_API_ONLY = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + BUILD_LIBRARY_FOR_DISTRIBUTION = NO; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 52R585FMZF; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu; + 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 */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 919434a..c4b79bd 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 18d9810..fc6bf80 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index f9b0d7c..af0309c 100644 --- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 15cada4..dc31a96 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,99 +1,99 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc1..17ccc03 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -1,10 +1,10 @@ - - - - - - - + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 18d9810..fc6bf80 100644 --- a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index f9b0d7c..af0309c 100644 --- a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -1,8 +1,8 @@ - - - - - PreviewsEnabled - - - + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6266644..8be1cec 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,13 +1,13 @@ -import Flutter -import UIKit - -@main -@objc class AppDelegate: FlutterAppDelegate { - override func application( - _ application: UIApplication, - didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? - ) -> Bool { - GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) - } -} +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..1950fd8 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +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" - } -} +{ + "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/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json index 0bedcf2..d08a4de 100644 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -1,23 +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" - } -} +{ + "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/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md index 89c2725..65a94b5 100644 --- a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -1,5 +1,5 @@ -# Launch Screen Assets - -You can customize the launch screen with your own desired assets by replacing the image files in this directory. - +# 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/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard index f2e259c..497371e 100644 --- a/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,37 +1,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index f3c2851..bbb83ca 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist index 4ccc399..5128f6f 100644 --- a/ios/Runner/GoogleService-Info.plist +++ b/ios/Runner/GoogleService-Info.plist @@ -1,30 +1,30 @@ - - - - - API_KEY - AIzaSyAKlnbPEKCXiANLM1g-M9v3KyjVdZQZLG0 - GCM_SENDER_ID - 615444202770 - PLIST_VERSION - 1 - BUNDLE_ID - com.example.myedu - PROJECT_ID - myedu-d90cd - STORAGE_BUCKET - myedu-d90cd.firebasestorage.app - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:615444202770:ios:a5d4dbba8b8b8cb0285fcb - + + + + + API_KEY + AIzaSyAKlnbPEKCXiANLM1g-M9v3KyjVdZQZLG0 + GCM_SENDER_ID + 615444202770 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.myedu + PROJECT_ID + myedu-d90cd + STORAGE_BUCKET + myedu-d90cd.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:615444202770:ios:a5d4dbba8b8b8cb0285fcb + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d3d52e8..2f0b071 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -1,49 +1,49 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - Myedu - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - myedu - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - - + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Myedu + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + myedu + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h index 308a2a5..fae207f 100644 --- a/ios/Runner/Runner-Bridging-Header.h +++ b/ios/Runner/Runner-Bridging-Header.h @@ -1 +1 @@ -#import "GeneratedPluginRegistrant.h" +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift index 86a7c3b..4d206de 100644 --- a/ios/RunnerTests/RunnerTests.swift +++ b/ios/RunnerTests/RunnerTests.swift @@ -1,12 +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. - } - -} +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/lib/components/custom_button.dart b/lib/components/custom_button.dart index 843b8e8..901f304 100644 --- a/lib/components/custom_button.dart +++ b/lib/components/custom_button.dart @@ -79,7 +79,7 @@ class _CustomButtonState extends State builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, - child: Container( + child: SizedBox( width: widget.width, height: widget.height, child: diff --git a/lib/components/custom_dropdown.dart b/lib/components/custom_dropdown.dart index 1c3317c..0894fed 100644 --- a/lib/components/custom_dropdown.dart +++ b/lib/components/custom_dropdown.dart @@ -26,7 +26,7 @@ class CustomDropdown extends StatelessWidget { return Container( margin: const EdgeInsets.symmetric(vertical: 8.0), child: DropdownButtonFormField( - value: value, + initialValue: value, onChanged: onChanged, validator: validator ?? _defaultValidator, isExpanded: true, @@ -41,7 +41,7 @@ class CustomDropdown extends StatelessWidget { enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide( - color: Theme.of(context).primaryColor.withOpacity(0.3), + color: Theme.of(context).primaryColor.withValues(alpha: 0.3), ), ), focusedBorder: OutlineInputBorder( diff --git a/lib/components/custom_text_field.dart b/lib/components/custom_text_field.dart index d10308f..eed2db7 100644 --- a/lib/components/custom_text_field.dart +++ b/lib/components/custom_text_field.dart @@ -1,144 +1,144 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:myedu/constraints/app_constants.dart'; - -class CustomTextField extends StatefulWidget { - final TextEditingController controller; - final String label; - final String? hint; - final bool isPassword; - final bool isEmail; - final bool isPhone; - final IconData? prefixIcon; - final String? Function(String?)? validator; - final TextInputType? keyboardType; - final List? inputFormatters; - final int maxLines; - final bool enabled; - final VoidCallback? onTap; - final Function(String)? onChanged; - - const CustomTextField({ - super.key, - required this.controller, - required this.label, - this.hint, - this.isPassword = false, - this.isEmail = false, - this.isPhone = false, - this.prefixIcon, - this.validator, - this.keyboardType, - this.inputFormatters, - this.maxLines = 1, - this.enabled = true, - this.onTap, - this.onChanged, - }); - - @override - State createState() => _CustomTextFieldState(); -} - -class _CustomTextFieldState extends State - with SingleTickerProviderStateMixin { - bool _obscureText = true; - late AnimationController _animationController; - late Animation _animation; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - duration: Duration(microseconds: 200), - vsync: this, - ); - _animation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), - ); - _animationController.forward(); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return FadeTransition( - opacity: _animation, - child: SlideTransition( - position: Tween( - begin: const Offset(0, 0.1), - end: Offset.zero, - ).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeOut), - ), - child: TextFormField( - controller: widget.controller, - obscureText: widget.isPassword ? _obscureText : false, - keyboardType: - widget.keyboardType ?? - (widget.isEmail - ? TextInputType.emailAddress - : widget.isPhone - ? TextInputType.phone - : TextInputType.text), - inputFormatters: widget.inputFormatters, - maxLines: widget.maxLines, - enabled: widget.enabled, - onTap: widget.onTap, - onChanged: widget.onChanged, - validator: - widget.validator ?? - (widget.isEmail ? _emailValidator : _defaultValidator), - decoration: InputDecoration( - labelText: widget.label, - hintText: widget.hint, - prefixIcon: - widget.prefixIcon != null - ? Icon( - widget.prefixIcon, - color: Theme.of(context).primaryColor, - ) - : null, - suffixIcon: - widget.isPassword - ? IconButton( - icon: Icon( - _obscureText ? Icons.visibility_off : Icons.visibility, - color: Theme.of(context).primaryColor, - ), - onPressed: () { - setState(() { - _obscureText = !_obscureText; - }); - }, - ) - : null, - ), - ), - ), - ); - } - - String? _emailValidator(String? value) { - if (value == null || value.isEmpty) { - return AppConstants.emailRequired; - } - if (!GetUtils.isEmail(value)) { - return AppConstants.emailInvalid; - } - return null; - } - - String? _defaultValidator(String? value) { - if (value == null || value.isEmpty) { - return '${widget.label} is required'; - } - return null; - } -} +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:myedu/constraints/app_constants.dart'; + +class CustomTextField extends StatefulWidget { + final TextEditingController controller; + final String label; + final String? hint; + final bool isPassword; + final bool isEmail; + final bool isPhone; + final IconData? prefixIcon; + final String? Function(String?)? validator; + final TextInputType? keyboardType; + final List? inputFormatters; + final int maxLines; + final bool enabled; + final VoidCallback? onTap; + final Function(String)? onChanged; + + const CustomTextField({ + super.key, + required this.controller, + required this.label, + this.hint, + this.isPassword = false, + this.isEmail = false, + this.isPhone = false, + this.prefixIcon, + this.validator, + this.keyboardType, + this.inputFormatters, + this.maxLines = 1, + this.enabled = true, + this.onTap, + this.onChanged, + }); + + @override + State createState() => _CustomTextFieldState(); +} + +class _CustomTextFieldState extends State + with SingleTickerProviderStateMixin { + bool _obscureText = true; + late AnimationController _animationController; + late Animation _animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: Duration(microseconds: 200), + vsync: this, + ); + _animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: _animation, + child: SlideTransition( + position: Tween( + begin: const Offset(0, 0.1), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeOut), + ), + child: TextFormField( + controller: widget.controller, + obscureText: widget.isPassword ? _obscureText : false, + keyboardType: + widget.keyboardType ?? + (widget.isEmail + ? TextInputType.emailAddress + : widget.isPhone + ? TextInputType.phone + : TextInputType.text), + inputFormatters: widget.inputFormatters, + maxLines: widget.maxLines, + enabled: widget.enabled, + onTap: widget.onTap, + onChanged: widget.onChanged, + validator: + widget.validator ?? + (widget.isEmail ? _emailValidator : _defaultValidator), + decoration: InputDecoration( + labelText: widget.label, + hintText: widget.hint, + prefixIcon: + widget.prefixIcon != null + ? Icon( + widget.prefixIcon, + color: Theme.of(context).primaryColor, + ) + : null, + suffixIcon: + widget.isPassword + ? IconButton( + icon: Icon( + _obscureText ? Icons.visibility_off : Icons.visibility, + color: Theme.of(context).primaryColor, + ), + onPressed: () { + setState(() { + _obscureText = !_obscureText; + }); + }, + ) + : null, + ), + ), + ), + ); + } + + String? _emailValidator(String? value) { + if (value == null || value.isEmpty) { + return AppConstants.emailRequired; + } + if (!GetUtils.isEmail(value)) { + return AppConstants.emailInvalid; + } + return null; + } + + String? _defaultValidator(String? value) { + if (value == null || value.isEmpty) { + return '${widget.label} is required'; + } + return null; + } +} diff --git a/lib/constraints/app_constants.dart b/lib/constraints/app_constants.dart index 117c9b1..60b8acd 100644 --- a/lib/constraints/app_constants.dart +++ b/lib/constraints/app_constants.dart @@ -53,6 +53,17 @@ class AppConstants { // Firebase Collections static const String usersCollection = 'users'; + // SSLCommerz Sandbox + static const String sslCommerzStoreId = 'azrul6a3d495f05244'; + static const String sslCommerzStorePassword = 'azrul6a3d495f05244@ssl'; + static const String sslCommerzRegisteredUrl = 'https://azrul.is-a.dev/'; + static const String sslCommerzSessionApi = + 'https://sandbox.sslcommerz.com/gwprocess/v4/api.php'; + static const String sslCommerzValidationApi = + 'https://sandbox.sslcommerz.com/validator/api/validationserverAPI.php'; + static const String sslCommerzIpnUrl = + '${sslCommerzRegisteredUrl}sslcommerz/ipn'; + // Validation Messages static const String emailRequired = 'Email is required'; static const String emailInvalid = 'Please enter a valid email'; diff --git a/lib/controllers/academic_fees_controller.dart b/lib/controllers/academic_fees_controller.dart index 85ac2e7..0c770a6 100644 --- a/lib/controllers/academic_fees_controller.dart +++ b/lib/controllers/academic_fees_controller.dart @@ -1,10 +1,13 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_sslcommerz/model/SSLCAdditionalInitializer.dart'; +import 'package:flutter_sslcommerz/model/SSLCCustomerInfoInitializer.dart'; import 'package:flutter_sslcommerz/model/SSLCSdkType.dart'; import 'package:flutter_sslcommerz/model/SSLCommerzInitialization.dart'; import 'package:flutter_sslcommerz/model/SSLCurrencyType.dart'; import 'package:flutter_sslcommerz/sslcommerz.dart'; +import '../constraints/app_constants.dart'; import '../models/academic_fee_model.dart'; import '../models/user_model.dart'; import '../services/pdf_receipt_service.dart'; @@ -205,28 +208,55 @@ class AcademicFeesController extends GetxController { isLoading.value = true; try { - // Initialize SSL Commerz - Sslcommerz sslcommerz = Sslcommerz( - initializer: SSLCommerzInitialization( - multi_card_name: "visa,master,bkash,nagad", - currency: SSLCurrencyType.BDT, - product_category: "Education", - sdkType: SSLCSdkType.TESTBOX, - store_id: "algor670ab401dbbcc", - store_passwd: "algor670ab401dbbcc@ssl", - total_amount: fee.totalAmount, - tran_id: DateTime.now().millisecondsSinceEpoch.toString(), - ), - ); + final tranId = 'MYEDU-${fee.id}-${DateTime.now().millisecondsSinceEpoch}'; + + final sslcommerz = Sslcommerz( + initializer: SSLCommerzInitialization( + ipn_url: AppConstants.sslCommerzIpnUrl, + multi_card_name: 'visa,master,bkash,nagad', + currency: SSLCurrencyType.BDT, + product_category: 'Education', + sdkType: SSLCSdkType.TESTBOX, + store_id: AppConstants.sslCommerzStoreId, + store_passwd: AppConstants.sslCommerzStorePassword, + total_amount: fee.totalAmount, + tran_id: tranId, + ), + ) + .addCustomerInfoInitializer( + customerInfoInitializer: SSLCCustomerInfoInitializer( + customerName: _fallback(currentUser.name, 'MyEdu Student'), + customerEmail: _fallback( + currentUser.email, + 'student@myedu.local', + ), + customerAddress1: _fallback(currentUser.faculty, 'Campus'), + customerCity: 'Dhaka', + customerState: 'Dhaka', + customerPostCode: '1200', + customerCountry: 'Bangladesh', + customerPhone: _fallback(currentUser.phone, '01700000000'), + ), + ) + .addAdditionalInitializer( + sslcAdditionalInitializer: SSLCAdditionalInitializer( + valueA: fee.id, + valueB: currentUser.uid, + valueC: currentUser.id, + valueD: fee.purpose, + bill_number: fee.id, + invoice_id: tranId, + ), + ); var result = await sslcommerz.payNow(); - if (result.status == 'VALID') { + if (_isSuccessfulPaymentStatus(result.status)) { // Update the fee status to paid final updatedFee = fee.copyWith( status: FeeStatus.paid, paidAt: DateTime.now(), - receiptNumber: result.tranId, + receiptNumber: result.tranId ?? tranId, ); await _firestore @@ -264,6 +294,7 @@ class AcademicFeesController extends GetxController { 'card_type': result.cardType, 'bank_tran_id': result.bankTranId, 'status': result.status, + 'store_id': AppConstants.sslCommerzStoreId, }, ), ); @@ -294,6 +325,16 @@ class AcademicFeesController extends GetxController { await processPayment(fee); } + bool _isSuccessfulPaymentStatus(String? status) { + final normalizedStatus = status?.toUpperCase(); + return normalizedStatus == 'VALID' || normalizedStatus == 'VALIDATED'; + } + + String _fallback(String? value, String fallback) { + final trimmed = value?.trim(); + return trimmed == null || trimmed.isEmpty ? fallback : trimmed; + } + void refreshFees() { fetchAcademicFees(); } diff --git a/lib/controllers/academic_result_controller.dart b/lib/controllers/academic_result_controller.dart index 9b76040..f2791bc 100644 --- a/lib/controllers/academic_result_controller.dart +++ b/lib/controllers/academic_result_controller.dart @@ -1,6 +1,8 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; +import '../controllers/auth_controller.dart'; +import '../data/sample_academic_results_data.dart'; import '../models/academic_result_model.dart'; class AcademicResultController extends GetxController { @@ -12,6 +14,7 @@ class AcademicResultController extends GetxController { final RxString errorMessage = ''.obs; final RxString selectedSemester = 'all'.obs; final RxString selectedYear = 'all'.obs; + final RxBool isUsingSampleResults = false.obs; // Statistics final RxDouble overallGPA = 0.0.obs; @@ -29,38 +32,47 @@ class AcademicResultController extends GetxController { try { isLoading.value = true; errorMessage.value = ''; + isUsingSampleResults.value = false; - print('Fetching academic results from Firestore...'); + final authController = Get.find(); + final currentUser = authController.user; + final studentId = currentUser?.id.trim() ?? ''; + + if (studentId.isEmpty) { + academicResults.value = _personalizedSampleResults(); + isUsingSampleResults.value = true; + _calculateStatistics(); + return; + } - // Query results for studentId "2002010" QuerySnapshot snapshot = await _firestore .collection('results') - .where('studentId', isEqualTo: '2002010') + .where('studentId', isEqualTo: studentId) .get(); - print('Found ${snapshot.docs.length} result documents'); - final results = snapshot.docs.map((doc) { final data = doc.data() as Map; - print('Document ${doc.id}: $data'); return AcademicResultModel.fromMap(doc.id, data); }).toList(); - academicResults.value = results; + if (results.isEmpty) { + academicResults.value = _personalizedSampleResults(); + isUsingSampleResults.value = true; + } else { + academicResults.value = results; + } + _calculateStatistics(); + } catch (e) { + academicResults.value = _personalizedSampleResults(); + isUsingSampleResults.value = true; _calculateStatistics(); - - print('Successfully loaded ${results.length} results'); - } catch (e, stackTrace) { - errorMessage.value = 'Failed to fetch academic results: $e'; - print('Error fetching results: $e'); - print('Stack trace: $stackTrace'); Get.snackbar( - 'Error', - 'Failed to load academic results: $e', + 'Showing Demo Results', + 'Could not load Firestore results, so sample academic results are shown.', snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, + backgroundColor: Colors.orange, colorText: Colors.white, ); } finally { @@ -68,6 +80,35 @@ class AcademicResultController extends GetxController { } } + List _personalizedSampleResults() { + final currentUser = Get.find().user; + final studentId = currentUser?.id.trim(); + final studentName = currentUser?.name.trim(); + final department = currentUser?.faculty.trim(); + + return SampleAcademicResultsData.getSampleAcademicResults().map((result) { + return AcademicResultModel( + id: + 'sample_${studentId ?? 'student'}_${result.semester}_${result.academicYear}', + studentId: + studentId == null || studentId.isEmpty + ? result.studentId + : studentId, + studentName: + studentName == null || studentName.isEmpty + ? result.studentName + : studentName, + semester: result.semester, + academicYear: result.academicYear, + department: + department == null || department.isEmpty + ? result.department + : department, + courses: result.courses, + ); + }).toList(); + } + void _calculateStatistics() { if (academicResults.isEmpty) { overallGPA.value = 0.0; @@ -100,21 +141,30 @@ class AcademicResultController extends GetxController { } List get filteredResults { - return academicResults.where((result) { - // Semester filter - if (selectedSemester.value != 'all' && - result.semester != selectedSemester.value) { - return false; - } - - // Year filter - if (selectedYear.value != 'all' && - result.academicYear != selectedYear.value) { - return false; - } - - return true; - }).toList(); + final results = + academicResults.where((result) { + // Semester filter + if (selectedSemester.value != 'all' && + result.semester != selectedSemester.value) { + return false; + } + + // Year filter + if (selectedYear.value != 'all' && + result.academicYear != selectedYear.value) { + return false; + } + + return true; + }).toList(); + + results.sort((a, b) { + final yearCompare = b.academicYear.compareTo(a.academicYear); + if (yearCompare != 0) return yearCompare; + return _semesterOrder(b.semester).compareTo(_semesterOrder(a.semester)); + }); + + return results; } List get availableSemesters { @@ -149,6 +199,11 @@ class AcademicResultController extends GetxController { fetchAcademicResults(); } + int _semesterOrder(String semester) { + final match = RegExp(r'\d+').firstMatch(semester); + return int.tryParse(match?.group(0) ?? '') ?? 0; + } + // Get color for grade Color getGradeColor(String grade) { switch (grade.toUpperCase()) { diff --git a/lib/controllers/auth_controller.dart b/lib/controllers/auth_controller.dart index 7068470..59cf8bb 100644 --- a/lib/controllers/auth_controller.dart +++ b/lib/controllers/auth_controller.dart @@ -1,526 +1,526 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:myedu/constraints/app_constants.dart'; -import 'package:myedu/models/user_model.dart'; -import 'package:myedu/services/email_service.dart'; -import 'dart:math'; - -class AuthController extends GetxController { - static AuthController get instance => Get.find(); - - final formKey = GlobalKey(); - final nameController = TextEditingController(); - final emailController = TextEditingController(); - final passwordController = TextEditingController(); - final confirmPasswordController = TextEditingController(); - final idController = TextEditingController(); - final regiController = TextEditingController(); - final phoneController = TextEditingController(); - - String? selectedFaculty; - - final FirebaseAuth _auth = FirebaseAuth.instance; - final FirebaseFirestore _firestore = FirebaseFirestore.instance; - final EmailService _emailService = EmailService(); - - // Normal variables instead of Rx - User? _firebaseUser; - UserModel? _user; - bool _isLoading = false; - bool _isAuthenticating = false; // Add this flag - - // OTP related variables - String? _currentOtp; - String? _pendingEmail; - DateTime? _otpExpiryTime; - - // Getters - User? get firebaseUser => _firebaseUser; - UserModel? get user => _user; - bool get isLoading => _isLoading; - bool get isLoggedIn => _firebaseUser != null; - - @override - void onInit() { - super.onInit(); - // _initAuthListener(); - } - - void _initAuthListener() { - _auth.authStateChanges().listen((User? user) async { - _firebaseUser = user; - - // Ensure navigation only occurs when not authenticating - if (!_isAuthenticating) { - await _setInitialScreen(user); - } - - update(); - }); - } - - Future updateProfilePicture(String imageUrl) async { - if (_firebaseUser == null) return; - - try { - await _firestore - .collection(AppConstants.usersCollection) - .doc(_firebaseUser!.uid) - .update({'profileImageUrl': imageUrl}); - - // Update local user model - if (_user != null) { - _user!.profileImageUrl = imageUrl; - update(); // Update UI - } - } catch (e) { - print('Error updating profile picture: $e'); - } - } - - Future loadUserData() async { - _firebaseUser = _auth.currentUser; - if (_firebaseUser != null) { - await _loadUserData(_firebaseUser!.uid); - } else { - _user = null; // Reset user if not logged in - } - update(); // Update UI - } - - Future _setInitialScreen(User? user) async { - if (user == null) { - // Navigate to sign-in screen only if not authenticating - if (!_isAuthenticating) { - Get.offAllNamed(AppRoutes.signIn); - } - } else { - await _loadUserData(user.uid); - Get.offAllNamed(AppRoutes.dashboard); - } - } - - Future _loadUserData(String uid) async { - try { - DocumentSnapshot doc = - await _firestore - .collection(AppConstants.usersCollection) - .doc(uid) - .get(); - - if (doc.exists) { - _user = UserModel.fromMap(doc.data() as Map); - update(); // Update UI - } - } catch (e) { - print('Error loading user data: $e'); - } - } - - // Sign Up with Email and Password - Future signUp({ - required String name, - required String email, - required String password, - required String faculty, - required String id, - required String regi, - required String phone, - }) async { - try { - _isLoading = true; - _isAuthenticating = true; // Set flag before auth operation - update(); - - // Create user with email and password - UserCredential userCredential = await _auth - .createUserWithEmailAndPassword(email: email, password: password); - - // Create user model - UserModel newUser = UserModel( - uid: userCredential.user!.uid, - name: name, - email: email, - faculty: faculty, - id: id, - regi: regi, - phone: phone, - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - ); - - // Save user data to Firestore - await _firestore - .collection(AppConstants.usersCollection) - .doc(userCredential.user!.uid) - .set(newUser.toMap()); - - _isAuthenticating = false; // Reset flag after successful sign-up - _isLoading = false; - update(); - return true; // Explicitly return true on success - } catch (e) { - _isAuthenticating = false; // Reset flag in case of error - _isLoading = false; - update(); - rethrow; // Rethrow the exception to propagate the error - } - } - - // Sign In with Email and Password - Future signIn({required String email, required String password}) async { - try { - _isLoading = true; - _isAuthenticating = true; // Set flag before auth operation - update(); - - UserCredential userCredential = await _auth.signInWithEmailAndPassword( - email: email, - password: password, - ); - - await _loadUserData(userCredential.user!.uid); - - Get.snackbar( - 'Success', - AppConstants.signInSuccess, - backgroundColor: Colors.green, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - - return true; - } on FirebaseAuthException catch (e) { - String message = _getFirebaseErrorMessage(e.code); - Get.snackbar( - 'Error', - message, - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return false; - } catch (e) { - Get.snackbar( - 'Error', - AppConstants.generalError, - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return false; - } finally { - _isLoading = false; - _isAuthenticating = false; // Clear flag after auth operation - update(); - } - } - - // Sign Out - Future signOut() async { - try { - _isLoading = true; - update(); // Update UI - - await _auth.signOut(); - _user = null; - update(); // Update UI - - Get.snackbar( - 'Success', - AppConstants.signOutSuccess, - backgroundColor: Colors.green, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } catch (e) { - Get.snackbar( - 'Error', - AppConstants.generalError, - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } finally { - _isLoading = false; - update(); // Update UI - } - } - - // Get Firebase error message - String _getFirebaseErrorMessage(String code) { - switch (code) { - case 'weak-password': - return 'The password provided is too weak.'; - case 'email-already-in-use': - return 'An account already exists for this email.'; - case 'user-not-found': - return 'No user found for this email.'; - case 'wrong-password': - return 'Wrong password provided.'; - case 'invalid-email': - return 'The email address is not valid.'; - case 'user-disabled': - return 'This user account has been disabled.'; - case 'too-many-requests': - return 'Too many requests. Try again later.'; - case 'operation-not-allowed': - return 'Signing in with Email and Password is not enabled.'; - default: - return 'An error occurred. Please try again.'; - } - } - - void signUpHandler() async { - if (formKey.currentState?.validate() ?? false) { - if (selectedFaculty == null) { - Get.snackbar( - 'Error', - AppConstants.facultyRequired, - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return; - } - - try { - // Send OTP email first - await sendOtpEmail(emailController.text.trim()); - - // Navigate to OTP verification screen - Get.toNamed( - AppRoutes.otpVerification, - arguments: { - 'email': emailController.text.trim(), - 'name': nameController.text.trim(), - 'faculty': selectedFaculty!, - 'id': idController.text.trim(), - 'regi': regiController.text.trim(), - 'phone': phoneController.text.trim(), - 'password': passwordController.text.trim(), - }, - ); - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to send OTP. Please try again.', - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } - } - } - - // Generate a random 6-digit OTP - String _generateOtp() { - Random random = Random(); - return (100000 + random.nextInt(900000)).toString(); - } - - // Send OTP email - Future sendOtpEmail(String email) async { - try { - _isLoading = true; - update(); - - // Generate new OTP - _currentOtp = _generateOtp(); - _pendingEmail = email; - _otpExpiryTime = DateTime.now().add(const Duration(minutes: 5)); - - // Send OTP email - await _emailService.sendOtpEmail(email, _currentOtp!); - - Get.snackbar( - 'Success', - 'OTP sent to your email successfully!', - backgroundColor: Colors.green, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to send OTP. Please try again.', - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - rethrow; - } finally { - _isLoading = false; - update(); - } - } - - // Verify OTP and complete sign-up - Future verifyOtpAndSignUp({ - required String otp, - required String name, - required String email, - required String password, - required String faculty, - required String id, - required String regi, - required String phone, - }) async { - try { - _isLoading = true; - update(); - - // Check if OTP is valid - if (_currentOtp == null || _pendingEmail != email) { - Get.snackbar( - 'Error', - 'Invalid OTP session. Please request a new OTP.', - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return false; - } - - // Check if OTP has expired - if (_otpExpiryTime == null || DateTime.now().isAfter(_otpExpiryTime!)) { - Get.snackbar( - 'Error', - 'OTP has expired. Please request a new OTP.', - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return false; - } - - // Verify OTP - if (otp != _currentOtp) { - Get.snackbar( - 'Error', - 'Invalid OTP code. Please try again.', - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return false; - } - - // OTP is valid, proceed with sign-up - _isAuthenticating = true; - update(); - - // Create user with email and password - UserCredential userCredential = await _auth - .createUserWithEmailAndPassword(email: email, password: password); - - // Create user model - UserModel newUser = UserModel( - uid: userCredential.user!.uid, - name: name, - email: email, - faculty: faculty, - id: id, - regi: regi, - phone: phone, - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - ); - - // Save user data to Firestore - await _firestore - .collection(AppConstants.usersCollection) - .doc(userCredential.user!.uid) - .set(newUser.toMap()); - - // Clear OTP data - _currentOtp = null; - _pendingEmail = null; - _otpExpiryTime = null; - - _isAuthenticating = false; - _isLoading = false; - update(); - - Get.snackbar( - 'Success', - 'Account created successfully!', - backgroundColor: Colors.green, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - - return true; - } on FirebaseAuthException catch (e) { - String message = _getFirebaseErrorMessage(e.code); - Get.snackbar( - 'Error', - message, - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return false; - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to create account. Please try again.', - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - return false; - } finally { - _isLoading = false; - _isAuthenticating = false; - update(); - } - } -} +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:myedu/constraints/app_constants.dart'; +import 'package:myedu/models/user_model.dart'; +import 'package:myedu/services/email_service.dart'; +import 'dart:math'; + +class AuthController extends GetxController { + static AuthController get instance => Get.find(); + + final formKey = GlobalKey(); + final nameController = TextEditingController(); + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + final confirmPasswordController = TextEditingController(); + final idController = TextEditingController(); + final regiController = TextEditingController(); + final phoneController = TextEditingController(); + + String? selectedFaculty; + + final FirebaseAuth _auth = FirebaseAuth.instance; + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final EmailService _emailService = EmailService(); + + // Normal variables instead of Rx + User? _firebaseUser; + UserModel? _user; + bool _isLoading = false; + bool _isAuthenticating = false; // Add this flag + + // OTP related variables + String? _currentOtp; + String? _pendingEmail; + DateTime? _otpExpiryTime; + + // Getters + User? get firebaseUser => _firebaseUser; + UserModel? get user => _user; + bool get isLoading => _isLoading; + bool get isLoggedIn => _firebaseUser != null; + + @override + void onInit() { + super.onInit(); + // _initAuthListener(); + } + + void _initAuthListener() { + _auth.authStateChanges().listen((User? user) async { + _firebaseUser = user; + + // Ensure navigation only occurs when not authenticating + if (!_isAuthenticating) { + await _setInitialScreen(user); + } + + update(); + }); + } + + Future updateProfilePicture(String imageUrl) async { + if (_firebaseUser == null) return; + + try { + await _firestore + .collection(AppConstants.usersCollection) + .doc(_firebaseUser!.uid) + .update({'profileImageUrl': imageUrl}); + + // Update local user model + if (_user != null) { + _user!.profileImageUrl = imageUrl; + update(); // Update UI + } + } catch (e) { + print('Error updating profile picture: $e'); + } + } + + Future loadUserData() async { + _firebaseUser = _auth.currentUser; + if (_firebaseUser != null) { + await _loadUserData(_firebaseUser!.uid); + } else { + _user = null; // Reset user if not logged in + } + update(); // Update UI + } + + Future _setInitialScreen(User? user) async { + if (user == null) { + // Navigate to sign-in screen only if not authenticating + if (!_isAuthenticating) { + Get.offAllNamed(AppRoutes.signIn); + } + } else { + await _loadUserData(user.uid); + Get.offAllNamed(AppRoutes.dashboard); + } + } + + Future _loadUserData(String uid) async { + try { + DocumentSnapshot doc = + await _firestore + .collection(AppConstants.usersCollection) + .doc(uid) + .get(); + + if (doc.exists) { + _user = UserModel.fromMap(doc.data() as Map); + update(); // Update UI + } + } catch (e) { + print('Error loading user data: $e'); + } + } + + // Sign Up with Email and Password + Future signUp({ + required String name, + required String email, + required String password, + required String faculty, + required String id, + required String regi, + required String phone, + }) async { + try { + _isLoading = true; + _isAuthenticating = true; // Set flag before auth operation + update(); + + // Create user with email and password + UserCredential userCredential = await _auth + .createUserWithEmailAndPassword(email: email, password: password); + + // Create user model + UserModel newUser = UserModel( + uid: userCredential.user!.uid, + name: name, + email: email, + faculty: faculty, + id: id, + regi: regi, + phone: phone, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); + + // Save user data to Firestore + await _firestore + .collection(AppConstants.usersCollection) + .doc(userCredential.user!.uid) + .set(newUser.toMap()); + + _isAuthenticating = false; // Reset flag after successful sign-up + _isLoading = false; + update(); + return true; // Explicitly return true on success + } catch (e) { + _isAuthenticating = false; // Reset flag in case of error + _isLoading = false; + update(); + rethrow; // Rethrow the exception to propagate the error + } + } + + // Sign In with Email and Password + Future signIn({required String email, required String password}) async { + try { + _isLoading = true; + _isAuthenticating = true; // Set flag before auth operation + update(); + + UserCredential userCredential = await _auth.signInWithEmailAndPassword( + email: email, + password: password, + ); + + await _loadUserData(userCredential.user!.uid); + + Get.snackbar( + 'Success', + AppConstants.signInSuccess, + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + + return true; + } on FirebaseAuthException catch (e) { + String message = _getFirebaseErrorMessage(e.code); + Get.snackbar( + 'Error', + message, + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return false; + } catch (e) { + Get.snackbar( + 'Error', + AppConstants.generalError, + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return false; + } finally { + _isLoading = false; + _isAuthenticating = false; // Clear flag after auth operation + update(); + } + } + + // Sign Out + Future signOut() async { + try { + _isLoading = true; + update(); // Update UI + + await _auth.signOut(); + _user = null; + update(); // Update UI + + Get.snackbar( + 'Success', + AppConstants.signOutSuccess, + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + } catch (e) { + Get.snackbar( + 'Error', + AppConstants.generalError, + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + } finally { + _isLoading = false; + update(); // Update UI + } + } + + // Get Firebase error message + String _getFirebaseErrorMessage(String code) { + switch (code) { + case 'weak-password': + return 'The password provided is too weak.'; + case 'email-already-in-use': + return 'An account already exists for this email.'; + case 'user-not-found': + return 'No user found for this email.'; + case 'wrong-password': + return 'Wrong password provided.'; + case 'invalid-email': + return 'The email address is not valid.'; + case 'user-disabled': + return 'This user account has been disabled.'; + case 'too-many-requests': + return 'Too many requests. Try again later.'; + case 'operation-not-allowed': + return 'Signing in with Email and Password is not enabled.'; + default: + return 'An error occurred. Please try again.'; + } + } + + void signUpHandler() async { + if (formKey.currentState?.validate() ?? false) { + if (selectedFaculty == null) { + Get.snackbar( + 'Error', + AppConstants.facultyRequired, + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return; + } + + try { + // Send OTP email first + await sendOtpEmail(emailController.text.trim()); + + // Navigate to OTP verification screen + Get.toNamed( + AppRoutes.otpVerification, + arguments: { + 'email': emailController.text.trim(), + 'name': nameController.text.trim(), + 'faculty': selectedFaculty!, + 'id': idController.text.trim(), + 'regi': regiController.text.trim(), + 'phone': phoneController.text.trim(), + 'password': passwordController.text.trim(), + }, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to send OTP. Please try again.', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + } + } + } + + // Generate a random 6-digit OTP + String _generateOtp() { + Random random = Random(); + return (100000 + random.nextInt(900000)).toString(); + } + + // Send OTP email + Future sendOtpEmail(String email) async { + try { + _isLoading = true; + update(); + + // Generate new OTP + _currentOtp = _generateOtp(); + _pendingEmail = email; + _otpExpiryTime = DateTime.now().add(const Duration(minutes: 5)); + + // Send OTP email + await _emailService.sendOtpEmail(email, _currentOtp!); + + Get.snackbar( + 'Success', + 'OTP sent to your email successfully!', + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to send OTP. Please try again.', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + rethrow; + } finally { + _isLoading = false; + update(); + } + } + + // Verify OTP and complete sign-up + Future verifyOtpAndSignUp({ + required String otp, + required String name, + required String email, + required String password, + required String faculty, + required String id, + required String regi, + required String phone, + }) async { + try { + _isLoading = true; + update(); + + // Check if OTP is valid + if (_currentOtp == null || _pendingEmail != email) { + Get.snackbar( + 'Error', + 'Invalid OTP session. Please request a new OTP.', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return false; + } + + // Check if OTP has expired + if (_otpExpiryTime == null || DateTime.now().isAfter(_otpExpiryTime!)) { + Get.snackbar( + 'Error', + 'OTP has expired. Please request a new OTP.', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return false; + } + + // Verify OTP + if (otp != _currentOtp) { + Get.snackbar( + 'Error', + 'Invalid OTP code. Please try again.', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return false; + } + + // OTP is valid, proceed with sign-up + _isAuthenticating = true; + update(); + + // Create user with email and password + UserCredential userCredential = await _auth + .createUserWithEmailAndPassword(email: email, password: password); + + // Create user model + UserModel newUser = UserModel( + uid: userCredential.user!.uid, + name: name, + email: email, + faculty: faculty, + id: id, + regi: regi, + phone: phone, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); + + // Save user data to Firestore + await _firestore + .collection(AppConstants.usersCollection) + .doc(userCredential.user!.uid) + .set(newUser.toMap()); + + // Clear OTP data + _currentOtp = null; + _pendingEmail = null; + _otpExpiryTime = null; + + _isAuthenticating = false; + _isLoading = false; + update(); + + Get.snackbar( + 'Success', + 'Account created successfully!', + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + + return true; + } on FirebaseAuthException catch (e) { + String message = _getFirebaseErrorMessage(e.code); + Get.snackbar( + 'Error', + message, + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return false; + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to create account. Please try again.', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + return false; + } finally { + _isLoading = false; + _isAuthenticating = false; + update(); + } + } +} diff --git a/lib/controllers/class_schedule_controller.dart b/lib/controllers/class_schedule_controller.dart index 327c2f7..0737852 100644 --- a/lib/controllers/class_schedule_controller.dart +++ b/lib/controllers/class_schedule_controller.dart @@ -1,78 +1,78 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:get/get.dart'; -import '../models/class_schedule_model.dart'; - -class ClassScheduleController extends GetxController { - final FirebaseFirestore _firestore = FirebaseFirestore.instance; - - final Rx classSchedule = Rx(null); - final RxBool isLoading = false.obs; - final RxString errorMessage = ''.obs; - - @override - void onInit() { - super.onInit(); - fetchClassSchedule(); - } - - Future fetchClassSchedule() async { - try { - isLoading.value = true; - errorMessage.value = ''; - - // Fetch from routines/6th document - DocumentSnapshot doc = - await _firestore.collection('routines').doc('6th').get(); - - if (doc.exists && doc.data() != null) { - final data = doc.data() as Map; - classSchedule.value = ClassScheduleModel.fromJson(doc.id, data); - } else { - errorMessage.value = 'No class schedule found'; - } - } catch (e) { - errorMessage.value = 'Failed to fetch class schedule: $e'; - Get.snackbar('Error', 'Failed to fetch class schedule: $e'); - } finally { - isLoading.value = false; - } - } - - List getTodaysClasses() { - return classSchedule.value?.getTodaysClasses() ?? []; - } - - List getTomorrowsClasses() { - return classSchedule.value?.getTomorrowsClasses() ?? []; - } - - String getCurrentDayName() { - List weekdays = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - ]; - return weekdays[DateTime.now().weekday % 7]; - } - - String getTomorrowDayName() { - List weekdays = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - ]; - return weekdays[(DateTime.now().weekday + 1) % 7]; - } - - void refresh() { - fetchClassSchedule(); - } -} +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:get/get.dart'; +import '../models/class_schedule_model.dart'; + +class ClassScheduleController extends GetxController { + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + + final Rx classSchedule = Rx(null); + final RxBool isLoading = false.obs; + final RxString errorMessage = ''.obs; + + @override + void onInit() { + super.onInit(); + fetchClassSchedule(); + } + + Future fetchClassSchedule() async { + try { + isLoading.value = true; + errorMessage.value = ''; + + // Fetch from routines/6th document + DocumentSnapshot doc = + await _firestore.collection('routines').doc('6th').get(); + + if (doc.exists && doc.data() != null) { + final data = doc.data() as Map; + classSchedule.value = ClassScheduleModel.fromJson(doc.id, data); + } else { + errorMessage.value = 'No class schedule found'; + } + } catch (e) { + errorMessage.value = 'Failed to fetch class schedule: $e'; + Get.snackbar('Error', 'Failed to fetch class schedule: $e'); + } finally { + isLoading.value = false; + } + } + + List getTodaysClasses() { + return classSchedule.value?.getTodaysClasses() ?? []; + } + + List getTomorrowsClasses() { + return classSchedule.value?.getTomorrowsClasses() ?? []; + } + + String getCurrentDayName() { + List weekdays = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ]; + return weekdays[DateTime.now().weekday % 7]; + } + + String getTomorrowDayName() { + List weekdays = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ]; + return weekdays[(DateTime.now().weekday + 1) % 7]; + } + + void refresh() { + fetchClassSchedule(); + } +} diff --git a/lib/controllers/dashboard_controller.dart b/lib/controllers/dashboard_controller.dart index fcfb082..076f480 100644 --- a/lib/controllers/dashboard_controller.dart +++ b/lib/controllers/dashboard_controller.dart @@ -1,64 +1,64 @@ -import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:get/get.dart'; -import 'package:firebase_storage/firebase_storage.dart'; -import 'package:image_picker/image_picker.dart'; -import 'dart:io'; - -import 'package:myedu/controllers/auth_controller.dart'; - -class DashboardController extends GetxController { - var profileImageUrl = ''.obs; - var userName = 'Student Name'.obs; - var userEmail = 'student@university.edu'.obs; - var isLoading = false.obs; - - @override - void onInit() { - super.onInit(); - loadUserData(); - } - - void loadUserData() { - // Load user data from Firebase/local storage - // This is where you'll implement the actual user data loading - } - - Future uploadProfileImage() async { - try { - isLoading.value = true; - // Show attractive overlay loading - EasyLoading.show( - status: 'Uploading image...', - maskType: EasyLoadingMaskType.black, - dismissOnTap: false, - ); - - final ImagePicker picker = ImagePicker(); - final XFile? image = await picker.pickImage(source: ImageSource.gallery); - - if (image != null) { - // Upload to Firebase Storage - final Reference storageRef = FirebaseStorage.instance - .ref() - .child('profile_images') - .child('${DateTime.now().millisecondsSinceEpoch}.jpg'); - - await storageRef.putFile(File(image.path)); - final String downloadUrl = await storageRef.getDownloadURL(); - - profileImageUrl.value = downloadUrl; - AuthController.instance.updateProfilePicture(downloadUrl); - // Save to user profile in Firestore - - EasyLoading.showSuccess('Image uploaded successfully!'); - } else { - EasyLoading.dismiss(); - } - } catch (e) { - EasyLoading.dismiss(); - Get.snackbar('Error', 'Failed to upload image: $e'); - } finally { - isLoading.value = false; - } - } -} +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:get/get.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:image_picker/image_picker.dart'; +import 'dart:io'; + +import 'package:myedu/controllers/auth_controller.dart'; + +class DashboardController extends GetxController { + var profileImageUrl = ''.obs; + var userName = 'Student Name'.obs; + var userEmail = 'student@university.edu'.obs; + var isLoading = false.obs; + + @override + void onInit() { + super.onInit(); + loadUserData(); + } + + void loadUserData() { + // Load user data from Firebase/local storage + // This is where you'll implement the actual user data loading + } + + Future uploadProfileImage() async { + try { + isLoading.value = true; + // Show attractive overlay loading + EasyLoading.show( + status: 'Uploading image...', + maskType: EasyLoadingMaskType.black, + dismissOnTap: false, + ); + + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage(source: ImageSource.gallery); + + if (image != null) { + // Upload to Firebase Storage + final Reference storageRef = FirebaseStorage.instance + .ref() + .child('profile_images') + .child('${DateTime.now().millisecondsSinceEpoch}.jpg'); + + await storageRef.putFile(File(image.path)); + final String downloadUrl = await storageRef.getDownloadURL(); + + profileImageUrl.value = downloadUrl; + AuthController.instance.updateProfilePicture(downloadUrl); + // Save to user profile in Firestore + + EasyLoading.showSuccess('Image uploaded successfully!'); + } else { + EasyLoading.dismiss(); + } + } catch (e) { + EasyLoading.dismiss(); + Get.snackbar('Error', 'Failed to upload image: $e'); + } finally { + isLoading.value = false; + } + } +} diff --git a/lib/controllers/notice_controller.dart b/lib/controllers/notice_controller.dart index 1406a92..aad942d 100644 --- a/lib/controllers/notice_controller.dart +++ b/lib/controllers/notice_controller.dart @@ -1,28 +1,28 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:get/get.dart'; -import '../models/notice_model.dart'; - -class NoticeController extends GetxController { - final FirebaseFirestore _firestore = FirebaseFirestore.instance; - final RxList notices = [].obs; - - @override - void onInit() { - super.onInit(); - fetchNotices(); - } - - void fetchNotices() async { - try { - final QuerySnapshot snapshot = - await _firestore.collection('notices').get(); - final List fetchedNotices = - snapshot.docs.map((doc) { - return NoticeModel.fromJson(doc.data() as Map); - }).toList(); - notices.assignAll(fetchedNotices); - } catch (e) { - Get.snackbar('Error', 'Failed to fetch notices: $e'); - } - } -} +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:get/get.dart'; +import '../models/notice_model.dart'; + +class NoticeController extends GetxController { + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final RxList notices = [].obs; + + @override + void onInit() { + super.onInit(); + fetchNotices(); + } + + void fetchNotices() async { + try { + final QuerySnapshot snapshot = + await _firestore.collection('notices').get(); + final List fetchedNotices = + snapshot.docs.map((doc) { + return NoticeModel.fromJson(doc.data() as Map); + }).toList(); + notices.assignAll(fetchedNotices); + } catch (e) { + Get.snackbar('Error', 'Failed to fetch notices: $e'); + } + } +} diff --git a/lib/controllers/notification_controller.dart b/lib/controllers/notification_controller.dart index c5dbaa7..5c1c508 100644 --- a/lib/controllers/notification_controller.dart +++ b/lib/controllers/notification_controller.dart @@ -1,123 +1,123 @@ -import 'package:get/get.dart'; -import 'package:myedu/services/push_notification_service.dart'; - -class NotificationController extends GetxController { - final RxBool _isNotificationEnabled = true.obs; - final RxString _fcmToken = ''.obs; - final RxInt _notificationCount = 0.obs; - final RxList> _notifications = - >[].obs; - - bool get isNotificationEnabled => _isNotificationEnabled.value; - String get fcmToken => _fcmToken.value; - int get notificationCount => _notificationCount.value; - List> get notifications => _notifications.toList(); - - @override - void onInit() { - super.onInit(); - _initializeNotifications(); - } - - Future _initializeNotifications() async { - try { - await PushNotificationService.initialize(); - - PushNotificationService.setupNotificationActionListeners(); - - String? token = PushNotificationService.fcmToken; - if (token != null) { - _fcmToken.value = token; - } - - print('โœ… Notification Controller initialized successfully'); - } catch (e) { - print('โŒ Error initializing Notification Controller: $e'); - } - } - - Future toggleNotifications(bool enabled) async { - _isNotificationEnabled.value = enabled; - - if (enabled) { - await PushNotificationService.initialize(); - } else { - await PushNotificationService.unsubscribeFromTopic(); - } - } - - Future refreshFCMToken() async { - String? newToken = await PushNotificationService.refreshToken(); - if (newToken != null) { - _fcmToken.value = newToken; - } - } - - void addNotification({ - required String title, - required String body, - String? imageUrl, - Map? data, - }) { - _notifications.insert(0, { - 'id': DateTime.now().millisecondsSinceEpoch, - 'title': title, - 'body': body, - 'imageUrl': imageUrl, - 'data': data, - 'timestamp': DateTime.now().toIso8601String(), - 'isRead': false, - }); - - _notificationCount.value = _notifications.length; - } - - void markAsRead(int id) { - int index = _notifications.indexWhere( - (notification) => notification['id'] == id, - ); - if (index != -1) { - _notifications[index]['isRead'] = true; - _notifications.refresh(); - } - } - - void markAllAsRead() { - for (var notification in _notifications) { - notification['isRead'] = true; - } - _notifications.refresh(); - } - - void clearAllNotifications() { - _notifications.clear(); - _notificationCount.value = 0; - } - - void clearNotification(int id) { - _notifications.removeWhere((notification) => notification['id'] == id); - _notificationCount.value = _notifications.length; - } - - int get unreadCount { - return _notifications - .where((notification) => !notification['isRead']) - .length; - } - - Future sendTestNotification() async { - await PushNotificationService.sendTestNotification(); - - addNotification( - title: 'Test Notification', - body: 'This is a test notification from MyEdu', - data: {'screen': 'dashboard'}, - ); - } - - @override - void onClose() { - PushNotificationService.dispose(); - super.onClose(); - } -} +import 'package:get/get.dart'; +import 'package:myedu/services/push_notification_service.dart'; + +class NotificationController extends GetxController { + final RxBool _isNotificationEnabled = true.obs; + final RxString _fcmToken = ''.obs; + final RxInt _notificationCount = 0.obs; + final RxList> _notifications = + >[].obs; + + bool get isNotificationEnabled => _isNotificationEnabled.value; + String get fcmToken => _fcmToken.value; + int get notificationCount => _notificationCount.value; + List> get notifications => _notifications.toList(); + + @override + void onInit() { + super.onInit(); + _initializeNotifications(); + } + + Future _initializeNotifications() async { + try { + await PushNotificationService.initialize(); + + PushNotificationService.setupNotificationActionListeners(); + + String? token = PushNotificationService.fcmToken; + if (token != null) { + _fcmToken.value = token; + } + + print('โœ… Notification Controller initialized successfully'); + } catch (e) { + print('โŒ Error initializing Notification Controller: $e'); + } + } + + Future toggleNotifications(bool enabled) async { + _isNotificationEnabled.value = enabled; + + if (enabled) { + await PushNotificationService.initialize(); + } else { + await PushNotificationService.unsubscribeFromTopic(); + } + } + + Future refreshFCMToken() async { + String? newToken = await PushNotificationService.refreshToken(); + if (newToken != null) { + _fcmToken.value = newToken; + } + } + + void addNotification({ + required String title, + required String body, + String? imageUrl, + Map? data, + }) { + _notifications.insert(0, { + 'id': DateTime.now().millisecondsSinceEpoch, + 'title': title, + 'body': body, + 'imageUrl': imageUrl, + 'data': data, + 'timestamp': DateTime.now().toIso8601String(), + 'isRead': false, + }); + + _notificationCount.value = _notifications.length; + } + + void markAsRead(int id) { + int index = _notifications.indexWhere( + (notification) => notification['id'] == id, + ); + if (index != -1) { + _notifications[index]['isRead'] = true; + _notifications.refresh(); + } + } + + void markAllAsRead() { + for (var notification in _notifications) { + notification['isRead'] = true; + } + _notifications.refresh(); + } + + void clearAllNotifications() { + _notifications.clear(); + _notificationCount.value = 0; + } + + void clearNotification(int id) { + _notifications.removeWhere((notification) => notification['id'] == id); + _notificationCount.value = _notifications.length; + } + + int get unreadCount { + return _notifications + .where((notification) => !notification['isRead']) + .length; + } + + Future sendTestNotification() async { + await PushNotificationService.sendTestNotification(); + + addNotification( + title: 'Test Notification', + body: 'This is a test notification from MyEdu', + data: {'screen': 'dashboard'}, + ); + } + + @override + void onClose() { + PushNotificationService.dispose(); + super.onClose(); + } +} diff --git a/lib/controllers/notifications_controller.dart b/lib/controllers/notifications_controller.dart index 22a53f2..618eb0d 100644 --- a/lib/controllers/notifications_controller.dart +++ b/lib/controllers/notifications_controller.dart @@ -1,132 +1,132 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:get/get.dart'; -import '../models/notification_model.dart'; - -class NotificationsController extends GetxController { - final FirebaseFirestore _firestore = FirebaseFirestore.instance; - final RxList notifications = [].obs; - final RxBool isLoading = true.obs; - final RxInt unreadCount = 0.obs; - - @override - void onInit() { - super.onInit(); - fetchNotifications(); - } - - void fetchNotifications() async { - try { - isLoading.value = true; - final QuerySnapshot snapshot = - await _firestore - .collection('notifications') - .orderBy('timestamp', descending: true) - .get(); - - final List fetchedNotifications = - snapshot.docs - .map((doc) => NotificationModel.fromFirestore(doc)) - .toList(); - - notifications.assignAll(fetchedNotifications); - _updateUnreadCount(); - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to fetch notifications: $e', - snackPosition: SnackPosition.BOTTOM, - ); - } finally { - isLoading.value = false; - } - } - - void markAsRead(String notificationId) async { - try { - await _firestore.collection('notifications').doc(notificationId).update({ - 'isRead': true, - }); - - // Update local state - final index = notifications.indexWhere((n) => n.id == notificationId); - if (index != -1) { - notifications[index] = notifications[index].copyWith(isRead: true); - _updateUnreadCount(); - } - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to mark notification as read: $e', - snackPosition: SnackPosition.BOTTOM, - ); - } - } - - void markAllAsRead() async { - try { - final batch = _firestore.batch(); - for (final notification in notifications.where((n) => !n.isRead)) { - batch.update( - _firestore.collection('notifications').doc(notification.id), - {'isRead': true}, - ); - } - await batch.commit(); - - // Update local state - for (int i = 0; i < notifications.length; i++) { - if (!notifications[i].isRead) { - notifications[i] = notifications[i].copyWith(isRead: true); - } - } - _updateUnreadCount(); - - Get.snackbar( - 'Success', - 'All notifications marked as read', - snackPosition: SnackPosition.BOTTOM, - ); - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to mark all notifications as read: $e', - snackPosition: SnackPosition.BOTTOM, - ); - } - } - - void deleteNotification(String notificationId) async { - try { - await _firestore.collection('notifications').doc(notificationId).delete(); - - notifications.removeWhere((n) => n.id == notificationId); - _updateUnreadCount(); - - Get.snackbar( - 'Success', - 'Notification deleted', - snackPosition: SnackPosition.BOTTOM, - ); - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to delete notification: $e', - snackPosition: SnackPosition.BOTTOM, - ); - } - } - - void _updateUnreadCount() { - unreadCount.value = notifications.where((n) => !n.isRead).length; - } - - void refreshNotifications() { - fetchNotifications(); - } - - List get unreadNotifications => - notifications.where((n) => !n.isRead).toList(); - - List get readNotifications => - notifications.where((n) => n.isRead).toList(); -} +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:get/get.dart'; +import '../models/notification_model.dart'; + +class NotificationsController extends GetxController { + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final RxList notifications = [].obs; + final RxBool isLoading = true.obs; + final RxInt unreadCount = 0.obs; + + @override + void onInit() { + super.onInit(); + fetchNotifications(); + } + + void fetchNotifications() async { + try { + isLoading.value = true; + final QuerySnapshot snapshot = + await _firestore + .collection('notifications') + .orderBy('timestamp', descending: true) + .get(); + + final List fetchedNotifications = + snapshot.docs + .map((doc) => NotificationModel.fromFirestore(doc)) + .toList(); + + notifications.assignAll(fetchedNotifications); + _updateUnreadCount(); + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to fetch notifications: $e', + snackPosition: SnackPosition.BOTTOM, + ); + } finally { + isLoading.value = false; + } + } + + void markAsRead(String notificationId) async { + try { + await _firestore.collection('notifications').doc(notificationId).update({ + 'isRead': true, + }); + + // Update local state + final index = notifications.indexWhere((n) => n.id == notificationId); + if (index != -1) { + notifications[index] = notifications[index].copyWith(isRead: true); + _updateUnreadCount(); + } + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to mark notification as read: $e', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + void markAllAsRead() async { + try { + final batch = _firestore.batch(); + for (final notification in notifications.where((n) => !n.isRead)) { + batch.update( + _firestore.collection('notifications').doc(notification.id), + {'isRead': true}, + ); + } + await batch.commit(); + + // Update local state + for (int i = 0; i < notifications.length; i++) { + if (!notifications[i].isRead) { + notifications[i] = notifications[i].copyWith(isRead: true); + } + } + _updateUnreadCount(); + + Get.snackbar( + 'Success', + 'All notifications marked as read', + snackPosition: SnackPosition.BOTTOM, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to mark all notifications as read: $e', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + void deleteNotification(String notificationId) async { + try { + await _firestore.collection('notifications').doc(notificationId).delete(); + + notifications.removeWhere((n) => n.id == notificationId); + _updateUnreadCount(); + + Get.snackbar( + 'Success', + 'Notification deleted', + snackPosition: SnackPosition.BOTTOM, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to delete notification: $e', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + void _updateUnreadCount() { + unreadCount.value = notifications.where((n) => !n.isRead).length; + } + + void refreshNotifications() { + fetchNotifications(); + } + + List get unreadNotifications => + notifications.where((n) => !n.isRead).toList(); + + List get readNotifications => + notifications.where((n) => n.isRead).toList(); +} diff --git a/lib/controllers/profile_controller.dart b/lib/controllers/profile_controller.dart index 8576a92..b1fcbaa 100644 --- a/lib/controllers/profile_controller.dart +++ b/lib/controllers/profile_controller.dart @@ -1,167 +1,167 @@ -import 'dart:io'; - -import 'package:firebase_storage/firebase_storage.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:myedu/controllers/auth_controller.dart'; - -class ProfileController extends GetxController { - static ProfileController get instance => Get.find(); - - final FirebaseStorage _storage = FirebaseStorage.instance; - final FirebaseFirestore _firestore = FirebaseFirestore.instance; - final AuthController _authController = Get.find(); - - File? _selectedImage; - String? _profileImageUrl; - bool _isUploading = false; - double _uploadProgress = 0.0; - - // Getters - File? get selectedImage => _selectedImage; - String? get profileImageUrl => _profileImageUrl; - bool get isUploading => _isUploading; - double get uploadProgress => _uploadProgress; - - @override - void onInit() { - super.onInit(); - _loadProfileImage(); - } - - Future _loadProfileImage() async { - if (_authController.user == null) return; - - try { - final doc = - await _firestore - .collection('users') - .doc(_authController.user!.uid) - .get(); - - if (doc.exists && doc.data()!.containsKey('profileImageUrl')) { - _profileImageUrl = doc.data()!['profileImageUrl'] as String?; - update(); - } - } catch (e) { - print('Error loading profile image: $e'); - } - } - - Future pickImage(ImageSource source) async { - try { - final ImagePicker picker = ImagePicker(); - final XFile? image = await picker.pickImage( - source: source, - maxWidth: 1000, - maxHeight: 1000, - imageQuality: 85, - ); - - if (image != null) { - _selectedImage = File(image.path); - update(); - - // Upload the image automatically - await uploadProfileImage(); - } - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to pick image: $e', - snackPosition: SnackPosition.BOTTOM, - ); - } - } - - Future uploadProfileImage() async { - if (_selectedImage == null || _authController.user == null) return; - - try { - _isUploading = true; - _uploadProgress = 0.0; - update(); - - // Create a reference to the location where we'll store the file - final storageRef = _storage - .ref() - .child('profile_images') - .child('${_authController.user!.uid}.jpg'); - - // Start upload task - final uploadTask = storageRef.putFile(_selectedImage!); - - // Listen to upload progress - uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) { - _uploadProgress = snapshot.bytesTransferred / snapshot.totalBytes; - update(); - }); - - // Wait for upload to complete - await uploadTask.whenComplete(() {}); - - // Get the download URL - final downloadUrl = await storageRef.getDownloadURL(); - _profileImageUrl = downloadUrl; - - // Update user document with the profile image URL - await _firestore - .collection('users') - .doc(_authController.user!.uid) - .update({'profileImageUrl': downloadUrl}); - - _isUploading = false; - update(); - - Get.snackbar( - 'Success', - 'Profile image uploaded successfully', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - ); - } catch (e) { - _isUploading = false; - update(); - - Get.snackbar( - 'Error', - 'Failed to upload profile image: $e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - } - } - - void showImageSourceDialog() { - Get.dialog( - AlertDialog( - title: const Text('Select Image Source'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: const Icon(Icons.camera_alt), - title: const Text('Camera'), - onTap: () { - Get.back(); - pickImage(ImageSource.camera); - }, - ), - ListTile( - leading: const Icon(Icons.photo_library), - title: const Text('Gallery'), - onTap: () { - Get.back(); - pickImage(ImageSource.gallery); - }, - ), - ], - ), - ), - ); - } -} +import 'dart:io'; + +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:myedu/controllers/auth_controller.dart'; + +class ProfileController extends GetxController { + static ProfileController get instance => Get.find(); + + final FirebaseStorage _storage = FirebaseStorage.instance; + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final AuthController _authController = Get.find(); + + File? _selectedImage; + String? _profileImageUrl; + bool _isUploading = false; + double _uploadProgress = 0.0; + + // Getters + File? get selectedImage => _selectedImage; + String? get profileImageUrl => _profileImageUrl; + bool get isUploading => _isUploading; + double get uploadProgress => _uploadProgress; + + @override + void onInit() { + super.onInit(); + _loadProfileImage(); + } + + Future _loadProfileImage() async { + if (_authController.user == null) return; + + try { + final doc = + await _firestore + .collection('users') + .doc(_authController.user!.uid) + .get(); + + if (doc.exists && doc.data()!.containsKey('profileImageUrl')) { + _profileImageUrl = doc.data()!['profileImageUrl'] as String?; + update(); + } + } catch (e) { + print('Error loading profile image: $e'); + } + } + + Future pickImage(ImageSource source) async { + try { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage( + source: source, + maxWidth: 1000, + maxHeight: 1000, + imageQuality: 85, + ); + + if (image != null) { + _selectedImage = File(image.path); + update(); + + // Upload the image automatically + await uploadProfileImage(); + } + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to pick image: $e', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + + Future uploadProfileImage() async { + if (_selectedImage == null || _authController.user == null) return; + + try { + _isUploading = true; + _uploadProgress = 0.0; + update(); + + // Create a reference to the location where we'll store the file + final storageRef = _storage + .ref() + .child('profile_images') + .child('${_authController.user!.uid}.jpg'); + + // Start upload task + final uploadTask = storageRef.putFile(_selectedImage!); + + // Listen to upload progress + uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) { + _uploadProgress = snapshot.bytesTransferred / snapshot.totalBytes; + update(); + }); + + // Wait for upload to complete + await uploadTask.whenComplete(() {}); + + // Get the download URL + final downloadUrl = await storageRef.getDownloadURL(); + _profileImageUrl = downloadUrl; + + // Update user document with the profile image URL + await _firestore + .collection('users') + .doc(_authController.user!.uid) + .update({'profileImageUrl': downloadUrl}); + + _isUploading = false; + update(); + + Get.snackbar( + 'Success', + 'Profile image uploaded successfully', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } catch (e) { + _isUploading = false; + update(); + + Get.snackbar( + 'Error', + 'Failed to upload profile image: $e', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } + + void showImageSourceDialog() { + Get.dialog( + AlertDialog( + title: const Text('Select Image Source'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.camera_alt), + title: const Text('Camera'), + onTap: () { + Get.back(); + pickImage(ImageSource.camera); + }, + ), + ListTile( + leading: const Icon(Icons.photo_library), + title: const Text('Gallery'), + onTap: () { + Get.back(); + pickImage(ImageSource.gallery); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/controllers/theme_controller.dart b/lib/controllers/theme_controller.dart index 6ad5fed..d6f5d46 100644 --- a/lib/controllers/theme_controller.dart +++ b/lib/controllers/theme_controller.dart @@ -1,45 +1,45 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class ThemeController extends GetxController { - static ThemeController get instance => Get.find(); - - // Normal variable instead of Rx - bool _isDarkMode = false; - - bool get isDarkMode => _isDarkMode; - ThemeMode get themeMode => _isDarkMode ? ThemeMode.dark : ThemeMode.light; - - @override - void onInit() { - super.onInit(); - // Load saved theme preference - _loadThemeFromStorage(); - } - - void toggleTheme() { - _isDarkMode = !_isDarkMode; - Get.changeThemeMode(themeMode); - _saveThemeToStorage(); - update(); // Update UI - } - - void setTheme(bool isDark) { - _isDarkMode = isDark; - Get.changeThemeMode(themeMode); - _saveThemeToStorage(); - update(); // Update UI - } - - void _loadThemeFromStorage() { - // For now, we'll start with light theme - // You can implement SharedPreferences here to persist theme choice - _isDarkMode = false; - update(); // Update UI - } - - void _saveThemeToStorage() { - // You can implement SharedPreferences here to persist theme choice - // For now, we'll just use the in-memory value - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ThemeController extends GetxController { + static ThemeController get instance => Get.find(); + + // Normal variable instead of Rx + bool _isDarkMode = false; + + bool get isDarkMode => _isDarkMode; + ThemeMode get themeMode => _isDarkMode ? ThemeMode.dark : ThemeMode.light; + + @override + void onInit() { + super.onInit(); + // Load saved theme preference + _loadThemeFromStorage(); + } + + void toggleTheme() { + _isDarkMode = !_isDarkMode; + Get.changeThemeMode(themeMode); + _saveThemeToStorage(); + update(); // Update UI + } + + void setTheme(bool isDark) { + _isDarkMode = isDark; + Get.changeThemeMode(themeMode); + _saveThemeToStorage(); + update(); // Update UI + } + + void _loadThemeFromStorage() { + // For now, we'll start with light theme + // You can implement SharedPreferences here to persist theme choice + _isDarkMode = false; + update(); // Update UI + } + + void _saveThemeToStorage() { + // You can implement SharedPreferences here to persist theme choice + // For now, we'll just use the in-memory value + } +} diff --git a/lib/data/sample_academic_fees_data.dart b/lib/data/sample_academic_fees_data.dart index aac4224..b9a21d8 100644 --- a/lib/data/sample_academic_fees_data.dart +++ b/lib/data/sample_academic_fees_data.dart @@ -1,314 +1,314 @@ -// Sample data for testing the Academic Fees feature -// This file contains sample data structure that should be added to Firestore - -/* -To add sample data to your Firestore 'academic_fees' collection, use the following structure: - -Collection: academic_fees - -Document 1 (Shows for All): -{ - "purpose": "Semester Tuition Fee", - "amount": 25000, - "deadline": "2025-06-15T00:00:00Z", // Use Firestore Timestamp - "createdAt": "2025-05-01T00:00:00Z", // Use Firestore Timestamp - "status": "pending", - "type": "tuition", - "description": "Tuition fee for Spring 2025 semester", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": true, - "applicableFaculties": ["Computer Science", "Engineering", "Business"], - "lateFee": 500, - "discount": null, - "targetType": "All", - "targetValue": "", - "items": [ - { - "name": "Course Registration", - "amount": 15000, - "description": "Registration for all courses" - }, - { - "name": "Laboratory Access", - "amount": 5000, - "description": "Access to computer labs and equipment" - }, - { - "name": "Library Services", - "amount": 3000, - "description": "Digital and physical library access" - }, - { - "name": "Student Activities", - "amount": 2000, - "description": "Clubs, events, and extracurricular activities" - } - ] -} - -Document 2 (Shows for 6th): -{ - "purpose": "Registration Fee", - "amount": 2000, - "deadline": "2025-05-30T00:00:00Z", - "createdAt": "2025-05-01T00:00:00Z", - "status": "pending", - "type": "registration", - "description": "Annual registration fee for 2025", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": false, - "applicableFaculties": [], - "lateFee": 200, - "discount": null, - "targetType": "Class", - "targetValue": "6th", - "items": [ - { - "name": "Student ID Card", - "amount": 500, - "description": "New student identification card" - }, - { - "name": "Academic Records", - "amount": 800, - "description": "Transcript and certificate processing" - }, - { - "name": "Administrative Fee", - "amount": 700, - "description": "General administrative processing" - } - ] -} - -Document 3 (Shows for 20-21): -{ - "purpose": "Library Fee", - "amount": 1500, - "deadline": "2025-05-20T00:00:00Z", // Overdue date for testing - "createdAt": "2025-04-01T00:00:00Z", - "status": "pending", - "type": "library", - "description": "Annual library access and maintenance fee", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": true, - "applicableFaculties": [], - "lateFee": 150, - "discount": null, - "targetType": "Year", - "targetValue": "20-21", - "items": [ - { - "name": "Digital Library Access", - "amount": 800, - "description": "Online journals, e-books, and databases" - }, - { - "name": "Physical Library Services", - "amount": 500, - "description": "Book borrowing and reading room access" - }, - { - "name": "Printing & Photocopying", - "amount": 200, - "description": "Library printing and copying services" - } - ] -} - -Document 4 (Shows for 6th): -{ - "purpose": "Lab Fee - Chemistry", - "amount": 3000, - "deadline": "2025-07-01T00:00:00Z", - "createdAt": "2025-05-01T00:00:00Z", - "status": "paid", - "type": "laboratory", - "description": "Laboratory fee for Chemistry practical sessions", - "receiptNumber": "RCP1716720000000", - "paidAt": "2025-05-15T10:30:00Z", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": false, - "applicableFaculties": ["Chemistry", "Biochemistry"], - "lateFee": null, - "discount": 300, - "targetType": "Class", - "targetValue": "6th", - "items": [ - { - "name": "Lab Equipment Usage", - "amount": 1500, - "description": "Access to specialized chemistry equipment" - }, - { - "name": "Chemical Supplies", - "amount": 1000, - "description": "Reagents and consumables for experiments" - }, - { - "name": "Safety Equipment", - "amount": 500, - "description": "Protective gear and safety equipment" - } - ] -} - -Document 5 (Shows for 20-21): -{ - "purpose": "Sports Fee", - "amount": 800, - "deadline": "2025-06-10T00:00:00Z", - "createdAt": "2025-05-01T00:00:00Z", - "status": "pending", - "type": "sports", - "description": "Annual sports and recreational activities fee", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": true, - "applicableFaculties": [], - "lateFee": 100, - "discount": null, - "targetType": "Year", - "targetValue": "20-21", - "items": [ - { - "name": "Gym Membership", - "amount": 400, - "description": "Access to fitness center and gym equipment" - }, - { - "name": "Sports Equipment", - "amount": 250, - "description": "Access to sports equipment and gear" - }, - { - "name": "Tournament Participation", - "amount": 150, - "description": "Inter-university sports competitions" - } - ] -} - -Document 6 (Will NOT show - targetValue doesn't match criteria): -{ - "purpose": "Development Fee", - "amount": 5000, - "deadline": "2025-06-30T00:00:00Z", - "createdAt": "2025-05-01T00:00:00Z", - "status": "pending", - "type": "development", - "description": "Infrastructure development and maintenance fee", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": true, - "applicableFaculties": [], - "lateFee": 1000, - "discount": null, - "targetType": "Class", - "targetValue": "7th", // This will NOT show because it's not '6th' or '20-21' - "items": [ - { - "name": "Building Maintenance", - "amount": 2000, - "description": "Maintenance and upkeep of campus buildings" - }, - { - "name": "Technology Infrastructure", - "amount": 1500, - "description": "IT systems and network infrastructure" - }, - { - "name": "Campus Beautification", - "amount": 1000, - "description": "Landscaping and campus improvement" - }, - { - "name": "Security Systems", - "amount": 500, - "description": "Campus security and surveillance" - } - ] -} - -Document 7 (Shows for specific student with ID "2002010"): -{ - "purpose": "Late Fee - Special Assessment", - "amount": 2500, - "deadline": "2025-06-20T00:00:00Z", - "createdAt": "2025-05-15T00:00:00Z", - "status": "pending", - "type": "miscellaneous", - "description": "Special assessment fee for late submission of documents", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": false, - "applicableFaculties": [], - "lateFee": 500, - "discount": null, - "targetType": "Individual", - "targetValue": "2002010", // This will show ONLY for student with ID "2002010" - "items": [ - { - "name": "Document Processing", - "amount": 1500, - "description": "Processing fee for late document submission" - }, - { - "name": "Administrative Penalty", - "amount": 1000, - "description": "Penalty for late submission" - } - ] -} - -Document 8 (Shows for specific student with ID "2002011"): -{ - "purpose": "Outstanding Library Fine", - "amount": 800, - "deadline": "2025-06-25T00:00:00Z", - "createdAt": "2025-05-20T00:00:00Z", - "status": "pending", - "type": "miscellaneous", - "description": "Outstanding fine for overdue library books", - "semester": "Spring 2025", - "academicYear": "2024-25", - "isRecurring": false, - "applicableFaculties": [], - "lateFee": 100, - "discount": null, - "targetType": "Individual", - "targetValue": "2002011", // This will show ONLY for student with ID "2002011" - "items": [ - { - "name": "Overdue Book Fine", - "amount": 600, - "description": "Fine for overdue library books" - }, - { - "name": "Processing Fee", - "amount": 200, - "description": "Administrative processing fee" - } - ] -} - -FILTERING LOGIC: -- If targetType == 'All': Show the bill to everyone -- If targetType == 'Individual' AND targetValue == user's academic ID: Show only to that specific student -- If targetType != 'All' AND targetType != 'Individual' AND targetValue == '6th' OR targetValue == '20-21': Show the bill -- Otherwise: Do NOT show the bill - -To add this data: -1. Go to Firebase Console -2. Navigate to your Firestore Database -3. Create a collection named 'academic_fees' -4. Add documents with the above data structure -5. Make sure to convert date strings to Firestore Timestamps -6. The 'items' field should be an array of objects with name, amount, and optional description -7. Add the new 'targetType' and 'targetValue' fields for filtering -8. For Individual fees, set targetValue to the specific student's academic ID -*/ +// Sample data for testing the Academic Fees feature +// This file contains sample data structure that should be added to Firestore + +/* +To add sample data to your Firestore 'academic_fees' collection, use the following structure: + +Collection: academic_fees + +Document 1 (Shows for All): +{ + "purpose": "Semester Tuition Fee", + "amount": 25000, + "deadline": "2025-06-15T00:00:00Z", // Use Firestore Timestamp + "createdAt": "2025-05-01T00:00:00Z", // Use Firestore Timestamp + "status": "pending", + "type": "tuition", + "description": "Tuition fee for Spring 2025 semester", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": true, + "applicableFaculties": ["Computer Science", "Engineering", "Business"], + "lateFee": 500, + "discount": null, + "targetType": "All", + "targetValue": "", + "items": [ + { + "name": "Course Registration", + "amount": 15000, + "description": "Registration for all courses" + }, + { + "name": "Laboratory Access", + "amount": 5000, + "description": "Access to computer labs and equipment" + }, + { + "name": "Library Services", + "amount": 3000, + "description": "Digital and physical library access" + }, + { + "name": "Student Activities", + "amount": 2000, + "description": "Clubs, events, and extracurricular activities" + } + ] +} + +Document 2 (Shows for 6th): +{ + "purpose": "Registration Fee", + "amount": 2000, + "deadline": "2025-05-30T00:00:00Z", + "createdAt": "2025-05-01T00:00:00Z", + "status": "pending", + "type": "registration", + "description": "Annual registration fee for 2025", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": false, + "applicableFaculties": [], + "lateFee": 200, + "discount": null, + "targetType": "Class", + "targetValue": "6th", + "items": [ + { + "name": "Student ID Card", + "amount": 500, + "description": "New student identification card" + }, + { + "name": "Academic Records", + "amount": 800, + "description": "Transcript and certificate processing" + }, + { + "name": "Administrative Fee", + "amount": 700, + "description": "General administrative processing" + } + ] +} + +Document 3 (Shows for 20-21): +{ + "purpose": "Library Fee", + "amount": 1500, + "deadline": "2025-05-20T00:00:00Z", // Overdue date for testing + "createdAt": "2025-04-01T00:00:00Z", + "status": "pending", + "type": "library", + "description": "Annual library access and maintenance fee", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": true, + "applicableFaculties": [], + "lateFee": 150, + "discount": null, + "targetType": "Year", + "targetValue": "20-21", + "items": [ + { + "name": "Digital Library Access", + "amount": 800, + "description": "Online journals, e-books, and databases" + }, + { + "name": "Physical Library Services", + "amount": 500, + "description": "Book borrowing and reading room access" + }, + { + "name": "Printing & Photocopying", + "amount": 200, + "description": "Library printing and copying services" + } + ] +} + +Document 4 (Shows for 6th): +{ + "purpose": "Lab Fee - Chemistry", + "amount": 3000, + "deadline": "2025-07-01T00:00:00Z", + "createdAt": "2025-05-01T00:00:00Z", + "status": "paid", + "type": "laboratory", + "description": "Laboratory fee for Chemistry practical sessions", + "receiptNumber": "RCP1716720000000", + "paidAt": "2025-05-15T10:30:00Z", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": false, + "applicableFaculties": ["Chemistry", "Biochemistry"], + "lateFee": null, + "discount": 300, + "targetType": "Class", + "targetValue": "6th", + "items": [ + { + "name": "Lab Equipment Usage", + "amount": 1500, + "description": "Access to specialized chemistry equipment" + }, + { + "name": "Chemical Supplies", + "amount": 1000, + "description": "Reagents and consumables for experiments" + }, + { + "name": "Safety Equipment", + "amount": 500, + "description": "Protective gear and safety equipment" + } + ] +} + +Document 5 (Shows for 20-21): +{ + "purpose": "Sports Fee", + "amount": 800, + "deadline": "2025-06-10T00:00:00Z", + "createdAt": "2025-05-01T00:00:00Z", + "status": "pending", + "type": "sports", + "description": "Annual sports and recreational activities fee", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": true, + "applicableFaculties": [], + "lateFee": 100, + "discount": null, + "targetType": "Year", + "targetValue": "20-21", + "items": [ + { + "name": "Gym Membership", + "amount": 400, + "description": "Access to fitness center and gym equipment" + }, + { + "name": "Sports Equipment", + "amount": 250, + "description": "Access to sports equipment and gear" + }, + { + "name": "Tournament Participation", + "amount": 150, + "description": "Inter-university sports competitions" + } + ] +} + +Document 6 (Will NOT show - targetValue doesn't match criteria): +{ + "purpose": "Development Fee", + "amount": 5000, + "deadline": "2025-06-30T00:00:00Z", + "createdAt": "2025-05-01T00:00:00Z", + "status": "pending", + "type": "development", + "description": "Infrastructure development and maintenance fee", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": true, + "applicableFaculties": [], + "lateFee": 1000, + "discount": null, + "targetType": "Class", + "targetValue": "7th", // This will NOT show because it's not '6th' or '20-21' + "items": [ + { + "name": "Building Maintenance", + "amount": 2000, + "description": "Maintenance and upkeep of campus buildings" + }, + { + "name": "Technology Infrastructure", + "amount": 1500, + "description": "IT systems and network infrastructure" + }, + { + "name": "Campus Beautification", + "amount": 1000, + "description": "Landscaping and campus improvement" + }, + { + "name": "Security Systems", + "amount": 500, + "description": "Campus security and surveillance" + } + ] +} + +Document 7 (Shows for specific student with ID "2002010"): +{ + "purpose": "Late Fee - Special Assessment", + "amount": 2500, + "deadline": "2025-06-20T00:00:00Z", + "createdAt": "2025-05-15T00:00:00Z", + "status": "pending", + "type": "miscellaneous", + "description": "Special assessment fee for late submission of documents", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": false, + "applicableFaculties": [], + "lateFee": 500, + "discount": null, + "targetType": "Individual", + "targetValue": "2002010", // This will show ONLY for student with ID "2002010" + "items": [ + { + "name": "Document Processing", + "amount": 1500, + "description": "Processing fee for late document submission" + }, + { + "name": "Administrative Penalty", + "amount": 1000, + "description": "Penalty for late submission" + } + ] +} + +Document 8 (Shows for specific student with ID "2002011"): +{ + "purpose": "Outstanding Library Fine", + "amount": 800, + "deadline": "2025-06-25T00:00:00Z", + "createdAt": "2025-05-20T00:00:00Z", + "status": "pending", + "type": "miscellaneous", + "description": "Outstanding fine for overdue library books", + "semester": "Spring 2025", + "academicYear": "2024-25", + "isRecurring": false, + "applicableFaculties": [], + "lateFee": 100, + "discount": null, + "targetType": "Individual", + "targetValue": "2002011", // This will show ONLY for student with ID "2002011" + "items": [ + { + "name": "Overdue Book Fine", + "amount": 600, + "description": "Fine for overdue library books" + }, + { + "name": "Processing Fee", + "amount": 200, + "description": "Administrative processing fee" + } + ] +} + +FILTERING LOGIC: +- If targetType == 'All': Show the bill to everyone +- If targetType == 'Individual' AND targetValue == user's academic ID: Show only to that specific student +- If targetType != 'All' AND targetType != 'Individual' AND targetValue == '6th' OR targetValue == '20-21': Show the bill +- Otherwise: Do NOT show the bill + +To add this data: +1. Go to Firebase Console +2. Navigate to your Firestore Database +3. Create a collection named 'academic_fees' +4. Add documents with the above data structure +5. Make sure to convert date strings to Firestore Timestamps +6. The 'items' field should be an array of objects with name, amount, and optional description +7. Add the new 'targetType' and 'targetValue' fields for filtering +8. For Individual fees, set targetValue to the specific student's academic ID +*/ diff --git a/lib/main.dart b/lib/main.dart index 01b8d02..b2e4477 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,111 +1,112 @@ -import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:get/get.dart'; -import 'package:myedu/constraints/app_constants.dart'; -import 'package:myedu/controllers/auth_controller.dart'; -import 'package:myedu/controllers/notification_controller.dart'; -import 'package:myedu/controllers/theme_controller.dart'; -import 'package:myedu/firebase_options.dart'; -import 'package:myedu/services/push_notification_service.dart'; -import 'package:myedu/utils/theme.dart'; -import 'package:myedu/views/dashboard_view.dart'; -import 'package:myedu/views/notification_history_view.dart'; -import 'package:myedu/views/otp_verification_view.dart'; -import 'package:myedu/views/sign_in_view.dart'; -import 'package:myedu/views/sign_up_view.dart'; -import 'package:myedu/views/splash_view.dart'; -import 'package:myedu/views/notice_screen.dart'; - -void main() async { - // Ensure that plugin services are initialized so that Firebase can be used. - WidgetsFlutterBinding.ensureInitialized(); - - // Initialize Firebase - await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); - - // Set background message handler - FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); - - // Initialize controllers - Get.put(ThemeController()); - Get.put(AuthController()); - Get.put(NotificationController()); - - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return GetBuilder( - builder: (themeController) { - return GetMaterialApp( - builder: EasyLoading.init(), - title: AppConstants.appName, - debugShowCheckedModeBanner: false, - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, - themeMode: themeController.themeMode, - initialRoute: AppRoutes.splash, - getPages: [ - GetPage( - name: AppRoutes.splash, - page: () => const SplashView(), - transition: Transition.fadeIn, - transitionDuration: const Duration(milliseconds: 300), - ), - GetPage( - name: AppRoutes.signIn, - page: () => const SignInView(), - transition: Transition.rightToLeft, - transitionDuration: const Duration(milliseconds: 300), - ), - GetPage( - name: AppRoutes.signUp, - page: () => const SignUpView(), - transition: Transition.rightToLeft, - transitionDuration: const Duration(milliseconds: 300), - ), - GetPage( - name: AppRoutes.otpVerification, - page: - () => OtpVerificationView( - email: Get.arguments['email'], - name: Get.arguments['name'], - faculty: Get.arguments['faculty'], - id: Get.arguments['id'], - regi: Get.arguments['regi'], - phone: Get.arguments['phone'], - password: Get.arguments['password'], - ), - transition: Transition.rightToLeft, - transitionDuration: const Duration(milliseconds: 300), - ), - GetPage( - name: AppRoutes.dashboard, - page: () => const DashboardView(), - transition: Transition.fadeIn, - transitionDuration: const Duration(milliseconds: 300), - ), - GetPage( - name: '/notices', - page: () => NoticeScreen(), - transition: Transition.rightToLeft, - transitionDuration: const Duration(milliseconds: 300), - ), - GetPage( - name: '/notifications', - page: () => const NotificationHistoryView(), - transition: Transition.rightToLeft, - transitionDuration: const Duration(milliseconds: 300), - ), - ], - ); - }, - ); - } -} +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:get/get.dart'; +import 'package:myedu/constraints/app_constants.dart'; +import 'package:myedu/controllers/auth_controller.dart'; +import 'package:myedu/controllers/notification_controller.dart'; +import 'package:myedu/controllers/theme_controller.dart'; +import 'package:myedu/firebase_options.dart'; +import 'package:myedu/services/push_notification_service.dart'; +import 'package:myedu/utils/theme.dart'; +import 'package:myedu/views/dashboard_view.dart'; +import 'package:myedu/views/notification_history_view.dart'; +import 'package:myedu/views/otp_verification_view.dart'; +import 'package:myedu/views/sign_in_view.dart'; +import 'package:myedu/views/sign_up_view.dart'; +import 'package:myedu/views/splash_view.dart'; +import 'package:myedu/views/notice_screen.dart'; + +void main() async { + // Ensure that plugin services are initialized so that Firebase can be used. + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize Firebase + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + + // Set background message handler + FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler); + + // Initialize controllers + Get.put(ThemeController()); + Get.put(AuthController()); + Get.put(NotificationController()); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return GetBuilder( + builder: (themeController) { + return GetMaterialApp( + builder: EasyLoading.init(), + title: AppConstants.appName, + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: themeController.themeMode, + initialRoute: AppRoutes.splash, + getPages: [ + GetPage( + name: AppRoutes.splash, + page: () => const SplashView(), + transition: Transition.fadeIn, + transitionDuration: const Duration(milliseconds: 300), + ), + GetPage( + name: AppRoutes.signIn, + page: () => const SignInView(), + transition: Transition.rightToLeft, + transitionDuration: const Duration(milliseconds: 300), + ), + GetPage( + name: AppRoutes.signUp, + page: () => const SignUpView(), + transition: Transition.rightToLeft, + transitionDuration: const Duration(milliseconds: 300), + ), + GetPage( + name: AppRoutes.otpVerification, + page: + () => OtpVerificationView( + email: Get.arguments['email'], + name: Get.arguments['name'], + faculty: Get.arguments['faculty'], + id: Get.arguments['id'], + regi: Get.arguments['regi'], + phone: Get.arguments['phone'], + password: Get.arguments['password'], + ), + transition: Transition.rightToLeft, + transitionDuration: const Duration(milliseconds: 300), + ), + GetPage( + name: AppRoutes.dashboard, + page: () => const DashboardView(), + transition: Transition.fadeIn, + transitionDuration: const Duration(milliseconds: 300), + ), + GetPage( + name: '/notices', + page: () => NoticeScreen(), + transition: Transition.rightToLeft, + transitionDuration: const Duration(milliseconds: 300), + ), + GetPage( + name: '/notifications', + page: () => const NotificationHistoryView(), + transition: Transition.rightToLeft, + transitionDuration: const Duration(milliseconds: 300), + ), + ], + ); + + }, + ); + } +} diff --git a/lib/models/academic_fee.dart b/lib/models/academic_fee.dart index 8b13789..d3f5a12 100644 --- a/lib/models/academic_fee.dart +++ b/lib/models/academic_fee.dart @@ -1 +1 @@ - + diff --git a/lib/models/academic_fee_model.dart b/lib/models/academic_fee_model.dart index 604bf7d..a11fa2c 100644 --- a/lib/models/academic_fee_model.dart +++ b/lib/models/academic_fee_model.dart @@ -1,348 +1,348 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; - -enum FeeStatus { pending, paid, overdue } - -enum FeeType { - tuition, - registration, - exam, - library, - laboratory, - sports, - development, - miscellaneous, -} - -class FeeItem { - final String name; - final double amount; - final String? description; - - FeeItem({required this.name, required this.amount, this.description}); - - factory FeeItem.fromMap(Map map) { - return FeeItem( - name: map['detail'] ?? '', - amount: _parseAmount(map['amount'] ?? 0), - description: map['description'], - ); - } - - static double _parseAmount(dynamic value) { - if (value == null) return 0.0; - if (value is String) { - final cleanValue = value.replaceAll(RegExp(r'[เงณ$,\s]'), ''); - return double.tryParse(cleanValue) ?? 0.0; - } - return (value as num).toDouble(); - } - - Map toMap() { - return {'name': name, 'amount': amount, 'description': description}; - } -} - -class AcademicFeeModel { - final String id; - final String purpose; - final double amount; - final DateTime deadline; - final DateTime createdAt; - final FeeStatus status; - final FeeType type; - final String? description; - final String? receiptNumber; - final DateTime? paidAt; - final String semester; - final String academicYear; - final bool isRecurring; - final List applicableFaculties; - final double? lateFee; - final double? discount; - final List items; - final String targetType; - final String targetValue; - - AcademicFeeModel({ - required this.id, - required this.purpose, - required this.amount, - required this.deadline, - required this.createdAt, - required this.status, - required this.type, - this.description, - this.receiptNumber, - this.paidAt, - required this.semester, - required this.academicYear, - this.isRecurring = false, - this.applicableFaculties = const [], - this.lateFee, - this.discount, - this.items = const [], - this.targetType = 'All', - this.targetValue = '', - }); - - // Calculate total amount including late fee and discount - double get totalAmount { - double total = amount; - if (lateFee != null && isOverdue) { - total += lateFee!; - } - if (discount != null) { - total -= discount!; - } - return total; - } - - // Check if fee is overdue - bool get isOverdue { - return DateTime.now().isAfter(deadline) && status == FeeStatus.pending; - } - - // Days remaining until due date - int get daysRemaining { - if (status == FeeStatus.paid) return 0; - final difference = deadline.difference(DateTime.now()).inDays; - return difference < 0 ? 0 : difference; - } - - // Get status based on current date and payment status - FeeStatus get currentStatus { - if (status == FeeStatus.paid) return FeeStatus.paid; - if (DateTime.now().isAfter(deadline)) return FeeStatus.overdue; - return FeeStatus.pending; - } - - // Convert to Map for Firestore - Map toMap() { - return { - 'id': id, - 'purpose': purpose, - 'amount': amount, - 'deadline': Timestamp.fromDate(deadline), - 'createdAt': Timestamp.fromDate(createdAt), - 'status': status.toString().split('.').last, - 'type': type.toString().split('.').last, - 'description': description, - 'receiptNumber': receiptNumber, - 'paidAt': paidAt != null ? Timestamp.fromDate(paidAt!) : null, - 'semester': semester, - 'academicYear': academicYear, - 'isRecurring': isRecurring, - 'applicableFaculties': applicableFaculties, - 'lateFee': lateFee, - 'discount': discount, - 'items': items.map((item) => item.toMap()).toList(), - 'targetType': targetType, - 'targetValue': targetValue, - }; - } - - // Create from Firestore Map - Updated to handle your actual document structure - factory AcademicFeeModel.fromMap(String docId, Map map) { - return AcademicFeeModel( - id: docId, - purpose: map['purpose'] ?? map['title'] ?? '', - amount: _parseAmount(map['amount'] ?? map['total'] ?? 0), - deadline: _parseDate(map['deadline'] ?? map['dueDate']) ?? DateTime.now(), - createdAt: _parseDate(map['createdAt']) ?? DateTime.now(), - status: _parseStatus(map['status']), - type: _parseType(map['type'] ?? map['category'] ?? map['purpose']), - description: map['description'] ?? map['details'], - receiptNumber: map['receiptNumber'], - paidAt: _parseDate(map['paidAt']), - semester: map['semester'] ?? '', - academicYear: map['academicYear'] ?? map['year'] ?? '', - isRecurring: map['isRecurring'] ?? false, - applicableFaculties: _parseStringList(map['applicableFaculties']), - lateFee: _parseAmount(map['lateFee']), - discount: _parseAmount(map['discount']), - items: _parseItems(map['items'] ?? map['breakdown'] ?? map['details']), - targetType: map['targetType'] ?? 'All', - targetValue: map['targetValue'] ?? '', - ); - } - - static double _parseAmount(dynamic value) { - if (value == null) return 0.0; - if (value is String) { - // Remove currency symbols and parse - final cleanValue = value.replaceAll(RegExp(r'[เงณ$,\s]'), ''); - return double.tryParse(cleanValue) ?? 0.0; - } - return (value as num).toDouble(); - } - - static DateTime? _parseDate(dynamic value) { - if (value == null) return null; - if (value is Timestamp) return value.toDate(); - if (value is String) { - try { - return DateTime.parse(value); - } catch (e) { - return null; - } - } - return null; - } - - static List _parseStringList(dynamic value) { - if (value == null) return []; - if (value is List) { - return value.map((e) => e.toString()).toList(); - } - return []; - } - - static List _parseItems(dynamic value) { - if (value == null) return []; - if (value is List) { - return value - .map((item) { - if (item is Map) { - return FeeItem.fromMap(item); - } - return null; - }) - .where((item) => item != null) - .cast() - .toList(); - } - return []; - } - - static FeeStatus _parseStatus(String? status) { - switch (status?.toLowerCase()) { - case 'paid': - return FeeStatus.paid; - case 'overdue': - return FeeStatus.overdue; - case 'pending': - default: - return FeeStatus.pending; - } - } - - static FeeType _parseType(String? type) { - if (type == null) return FeeType.miscellaneous; - - final typeStr = type.toLowerCase(); - - // Check for tuition-related keywords - if (typeStr.contains('tuition') || - typeStr.contains('semester') || - typeStr.contains('academic')) { - return FeeType.tuition; - } - - // Check for registration-related keywords - if (typeStr.contains('registration') || - typeStr.contains('admission') || - typeStr.contains('enrollment')) { - return FeeType.registration; - } - - // Check for exam-related keywords - if (typeStr.contains('exam') || - typeStr.contains('test') || - typeStr.contains('final') || - typeStr.contains('midterm')) { - return FeeType.exam; - } - - // Check for library-related keywords - if (typeStr.contains('library') || typeStr.contains('book')) { - return FeeType.library; - } - - // Check for lab-related keywords - if (typeStr.contains('laboratory') || - typeStr.contains('lab') || - typeStr.contains('practical')) { - return FeeType.laboratory; - } - - // Check for sports-related keywords - if (typeStr.contains('sports') || - typeStr.contains('athletic') || - typeStr.contains('physical')) { - return FeeType.sports; - } - - // Check for development-related keywords - if (typeStr.contains('development') || - typeStr.contains('infrastructure') || - typeStr.contains('building')) { - return FeeType.development; - } - - // Direct matches - switch (typeStr) { - case 'tuition': - return FeeType.tuition; - case 'registration': - return FeeType.registration; - case 'exam': - return FeeType.exam; - case 'library': - return FeeType.library; - case 'laboratory': - case 'lab': - return FeeType.laboratory; - case 'sports': - return FeeType.sports; - case 'development': - return FeeType.development; - default: - return FeeType.miscellaneous; - } - } - - // Copy with method for updates - AcademicFeeModel copyWith({ - String? id, - String? purpose, - double? amount, - DateTime? deadline, - DateTime? createdAt, - FeeStatus? status, - FeeType? type, - String? description, - String? receiptNumber, - DateTime? paidAt, - String? semester, - String? academicYear, - bool? isRecurring, - List? applicableFaculties, - double? lateFee, - double? discount, - List? items, - String? targetType, - String? targetValue, - }) { - return AcademicFeeModel( - id: id ?? this.id, - purpose: purpose ?? this.purpose, - amount: amount ?? this.amount, - deadline: deadline ?? this.deadline, - createdAt: createdAt ?? this.createdAt, - status: status ?? this.status, - type: type ?? this.type, - description: description ?? this.description, - receiptNumber: receiptNumber ?? this.receiptNumber, - paidAt: paidAt ?? this.paidAt, - semester: semester ?? this.semester, - academicYear: academicYear ?? this.academicYear, - isRecurring: isRecurring ?? this.isRecurring, - applicableFaculties: applicableFaculties ?? this.applicableFaculties, - lateFee: lateFee ?? this.lateFee, - discount: discount ?? this.discount, - items: items ?? this.items, - targetType: targetType ?? this.targetType, - targetValue: targetValue ?? this.targetValue, - ); - } -} +import 'package:cloud_firestore/cloud_firestore.dart'; + +enum FeeStatus { pending, paid, overdue } + +enum FeeType { + tuition, + registration, + exam, + library, + laboratory, + sports, + development, + miscellaneous, +} + +class FeeItem { + final String name; + final double amount; + final String? description; + + FeeItem({required this.name, required this.amount, this.description}); + + factory FeeItem.fromMap(Map map) { + return FeeItem( + name: map['detail'] ?? '', + amount: _parseAmount(map['amount'] ?? 0), + description: map['description'], + ); + } + + static double _parseAmount(dynamic value) { + if (value == null) return 0.0; + if (value is String) { + final cleanValue = value.replaceAll(RegExp(r'[เงณ$,\s]'), ''); + return double.tryParse(cleanValue) ?? 0.0; + } + return (value as num).toDouble(); + } + + Map toMap() { + return {'name': name, 'amount': amount, 'description': description}; + } +} + +class AcademicFeeModel { + final String id; + final String purpose; + final double amount; + final DateTime deadline; + final DateTime createdAt; + final FeeStatus status; + final FeeType type; + final String? description; + final String? receiptNumber; + final DateTime? paidAt; + final String semester; + final String academicYear; + final bool isRecurring; + final List applicableFaculties; + final double? lateFee; + final double? discount; + final List items; + final String targetType; + final String targetValue; + + AcademicFeeModel({ + required this.id, + required this.purpose, + required this.amount, + required this.deadline, + required this.createdAt, + required this.status, + required this.type, + this.description, + this.receiptNumber, + this.paidAt, + required this.semester, + required this.academicYear, + this.isRecurring = false, + this.applicableFaculties = const [], + this.lateFee, + this.discount, + this.items = const [], + this.targetType = 'All', + this.targetValue = '', + }); + + // Calculate total amount including late fee and discount + double get totalAmount { + double total = amount; + if (lateFee != null && isOverdue) { + total += lateFee!; + } + if (discount != null) { + total -= discount!; + } + return total; + } + + // Check if fee is overdue + bool get isOverdue { + return DateTime.now().isAfter(deadline) && status == FeeStatus.pending; + } + + // Days remaining until due date + int get daysRemaining { + if (status == FeeStatus.paid) return 0; + final difference = deadline.difference(DateTime.now()).inDays; + return difference < 0 ? 0 : difference; + } + + // Get status based on current date and payment status + FeeStatus get currentStatus { + if (status == FeeStatus.paid) return FeeStatus.paid; + if (DateTime.now().isAfter(deadline)) return FeeStatus.overdue; + return FeeStatus.pending; + } + + // Convert to Map for Firestore + Map toMap() { + return { + 'id': id, + 'purpose': purpose, + 'amount': amount, + 'deadline': Timestamp.fromDate(deadline), + 'createdAt': Timestamp.fromDate(createdAt), + 'status': status.toString().split('.').last, + 'type': type.toString().split('.').last, + 'description': description, + 'receiptNumber': receiptNumber, + 'paidAt': paidAt != null ? Timestamp.fromDate(paidAt!) : null, + 'semester': semester, + 'academicYear': academicYear, + 'isRecurring': isRecurring, + 'applicableFaculties': applicableFaculties, + 'lateFee': lateFee, + 'discount': discount, + 'items': items.map((item) => item.toMap()).toList(), + 'targetType': targetType, + 'targetValue': targetValue, + }; + } + + // Create from Firestore Map - Updated to handle your actual document structure + factory AcademicFeeModel.fromMap(String docId, Map map) { + return AcademicFeeModel( + id: docId, + purpose: map['purpose'] ?? map['title'] ?? '', + amount: _parseAmount(map['amount'] ?? map['total'] ?? 0), + deadline: _parseDate(map['deadline'] ?? map['dueDate']) ?? DateTime.now(), + createdAt: _parseDate(map['createdAt']) ?? DateTime.now(), + status: _parseStatus(map['status']), + type: _parseType(map['type'] ?? map['category'] ?? map['purpose']), + description: map['description'] ?? map['details'], + receiptNumber: map['receiptNumber'], + paidAt: _parseDate(map['paidAt']), + semester: map['semester'] ?? '', + academicYear: map['academicYear'] ?? map['year'] ?? '', + isRecurring: map['isRecurring'] ?? false, + applicableFaculties: _parseStringList(map['applicableFaculties']), + lateFee: _parseAmount(map['lateFee']), + discount: _parseAmount(map['discount']), + items: _parseItems(map['items'] ?? map['breakdown'] ?? map['details']), + targetType: map['targetType'] ?? 'All', + targetValue: map['targetValue'] ?? '', + ); + } + + static double _parseAmount(dynamic value) { + if (value == null) return 0.0; + if (value is String) { + // Remove currency symbols and parse + final cleanValue = value.replaceAll(RegExp(r'[เงณ$,\s]'), ''); + return double.tryParse(cleanValue) ?? 0.0; + } + return (value as num).toDouble(); + } + + static DateTime? _parseDate(dynamic value) { + if (value == null) return null; + if (value is Timestamp) return value.toDate(); + if (value is String) { + try { + return DateTime.parse(value); + } catch (e) { + return null; + } + } + return null; + } + + static List _parseStringList(dynamic value) { + if (value == null) return []; + if (value is List) { + return value.map((e) => e.toString()).toList(); + } + return []; + } + + static List _parseItems(dynamic value) { + if (value == null) return []; + if (value is List) { + return value + .map((item) { + if (item is Map) { + return FeeItem.fromMap(item); + } + return null; + }) + .where((item) => item != null) + .cast() + .toList(); + } + return []; + } + + static FeeStatus _parseStatus(String? status) { + switch (status?.toLowerCase()) { + case 'paid': + return FeeStatus.paid; + case 'overdue': + return FeeStatus.overdue; + case 'pending': + default: + return FeeStatus.pending; + } + } + + static FeeType _parseType(String? type) { + if (type == null) return FeeType.miscellaneous; + + final typeStr = type.toLowerCase(); + + // Check for tuition-related keywords + if (typeStr.contains('tuition') || + typeStr.contains('semester') || + typeStr.contains('academic')) { + return FeeType.tuition; + } + + // Check for registration-related keywords + if (typeStr.contains('registration') || + typeStr.contains('admission') || + typeStr.contains('enrollment')) { + return FeeType.registration; + } + + // Check for exam-related keywords + if (typeStr.contains('exam') || + typeStr.contains('test') || + typeStr.contains('final') || + typeStr.contains('midterm')) { + return FeeType.exam; + } + + // Check for library-related keywords + if (typeStr.contains('library') || typeStr.contains('book')) { + return FeeType.library; + } + + // Check for lab-related keywords + if (typeStr.contains('laboratory') || + typeStr.contains('lab') || + typeStr.contains('practical')) { + return FeeType.laboratory; + } + + // Check for sports-related keywords + if (typeStr.contains('sports') || + typeStr.contains('athletic') || + typeStr.contains('physical')) { + return FeeType.sports; + } + + // Check for development-related keywords + if (typeStr.contains('development') || + typeStr.contains('infrastructure') || + typeStr.contains('building')) { + return FeeType.development; + } + + // Direct matches + switch (typeStr) { + case 'tuition': + return FeeType.tuition; + case 'registration': + return FeeType.registration; + case 'exam': + return FeeType.exam; + case 'library': + return FeeType.library; + case 'laboratory': + case 'lab': + return FeeType.laboratory; + case 'sports': + return FeeType.sports; + case 'development': + return FeeType.development; + default: + return FeeType.miscellaneous; + } + } + + // Copy with method for updates + AcademicFeeModel copyWith({ + String? id, + String? purpose, + double? amount, + DateTime? deadline, + DateTime? createdAt, + FeeStatus? status, + FeeType? type, + String? description, + String? receiptNumber, + DateTime? paidAt, + String? semester, + String? academicYear, + bool? isRecurring, + List? applicableFaculties, + double? lateFee, + double? discount, + List? items, + String? targetType, + String? targetValue, + }) { + return AcademicFeeModel( + id: id ?? this.id, + purpose: purpose ?? this.purpose, + amount: amount ?? this.amount, + deadline: deadline ?? this.deadline, + createdAt: createdAt ?? this.createdAt, + status: status ?? this.status, + type: type ?? this.type, + description: description ?? this.description, + receiptNumber: receiptNumber ?? this.receiptNumber, + paidAt: paidAt ?? this.paidAt, + semester: semester ?? this.semester, + academicYear: academicYear ?? this.academicYear, + isRecurring: isRecurring ?? this.isRecurring, + applicableFaculties: applicableFaculties ?? this.applicableFaculties, + lateFee: lateFee ?? this.lateFee, + discount: discount ?? this.discount, + items: items ?? this.items, + targetType: targetType ?? this.targetType, + targetValue: targetValue ?? this.targetValue, + ); + } +} diff --git a/lib/models/academic_result_model.dart b/lib/models/academic_result_model.dart index ff1fc98..1fe9daa 100644 --- a/lib/models/academic_result_model.dart +++ b/lib/models/academic_result_model.dart @@ -116,7 +116,12 @@ class AcademicResultModel { static List _parseCourses(dynamic value) { if (value == null) return []; if (value is List) { - return value.map((course) => CourseResult.fromMap(course)).toList(); + return value + .whereType() + .map( + (course) => CourseResult.fromMap(Map.from(course)), + ) + .toList(); } return []; } diff --git a/lib/models/class_schedule_model.dart b/lib/models/class_schedule_model.dart index 2fdbe11..95fb66a 100644 --- a/lib/models/class_schedule_model.dart +++ b/lib/models/class_schedule_model.dart @@ -1,195 +1,195 @@ -class ClassScheduleModel { - final String id; - final Map> weeklySchedule; - - ClassScheduleModel({required this.id, required this.weeklySchedule}); - - factory ClassScheduleModel.fromJson(String id, Map json) { - Map> schedule = {}; - - // Parse each day of the week - json.forEach((day, timeSlots) { - if (timeSlots is Map) { - List daySlots = []; - - // Extract time slots and corresponding course details - timeSlots.forEach((key, value) { - if (key.startsWith('course_')) { - // Skip course keys, they are handled with time slots - return; - } - - if (value is String) { - // Find the corresponding course key (e.g., course_0 for 0) - String courseKey = 'course_$key'; - String courseName = timeSlots[courseKey] ?? ''; - - daySlots.add( - TimeSlot( - timeRange: value, - subject: courseName, - teacher: '', // Add teacher if available in the structure - room: '', // Add room if available in the structure - code: courseName, - ), - ); - } - }); - - schedule[day] = daySlots; - } - }); - - return ClassScheduleModel(id: id, weeklySchedule: schedule); - } - - Map toJson() { - Map result = {}; - - weeklySchedule.forEach((day, timeSlots) { - Map dayData = {}; - for (TimeSlot slot in timeSlots) { - dayData[slot.timeRange] = slot.toJson(); - } - result[day] = dayData; - }); - - return result; - } - - // Get classes for today - List getTodaysClasses() { - String today = _getCurrentDayName(); - return weeklySchedule[today] ?? []; - } - - // Get classes for tomorrow - List getTomorrowsClasses() { - String tomorrow = _getTomorrowDayName(); - return weeklySchedule[tomorrow] ?? []; - } - - String _getCurrentDayName() { - List weekdays = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - ]; - return weekdays[DateTime.now().weekday % 7]; - } - - String _getTomorrowDayName() { - List weekdays = [ - 'Sunday', - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - ]; - return weekdays[(DateTime.now().weekday + 1) % 7]; - } -} - -class TimeSlot { - final String timeRange; - final String subject; - final String teacher; - final String room; - final String code; - - TimeSlot({ - required this.timeRange, - required this.subject, - required this.teacher, - required this.room, - required this.code, - }); - - factory TimeSlot.fromJson(String timeRange, Map json) { - return TimeSlot( - timeRange: timeRange, - subject: json['subject'] ?? '', - teacher: json['teacher'] ?? '', - room: json['room'] ?? '', - code: json['code'] ?? '', - ); - } - - Map toJson() { - return {'subject': subject, 'teacher': teacher, 'room': room, 'code': code}; - } - - // Parse start and end time from time range (e.g., "8:00-8:50") - DateTime? getStartTime() { - try { - String startTimeStr; - if (timeRange.contains('-')) { - startTimeStr = timeRange.split('-')[0]; - } else { - startTimeStr = timeRange; // It's a single time - } - List timeParts = startTimeStr.split(':'); - int hour = int.parse(timeParts[0]); - int minute = int.parse(timeParts[1]); - - DateTime now = DateTime.now(); - return DateTime(now.year, now.month, now.day, hour, minute); - } catch (e) { - // Consider logging this error - return null; - } - } - - DateTime? getEndTime() { - try { - if (timeRange.contains('-')) { - String endTimeStr = timeRange.split('-')[1]; - List timeParts = endTimeStr.split(':'); - int hour = int.parse(timeParts[0]); - int minute = int.parse(timeParts[1]); - - DateTime now = DateTime.now(); - return DateTime(now.year, now.month, now.day, hour, minute); - } else { - // No end time specified, calculate as start_time + 1 hour (default duration) - DateTime? startTime = getStartTime(); - if (startTime != null) { - return startTime.add( - const Duration(hours: 1), - ); // Default 1 hour duration - } - return null; - } - } catch (e) { - // Consider logging this error - return null; - } - } - - bool isCurrentClass() { - DateTime now = DateTime.now(); - DateTime? start = getStartTime(); - DateTime? end = getEndTime(); - - if (start != null && end != null) { - return now.isAfter(start) && now.isBefore(end); - } - return false; - } - - bool isUpcoming() { - DateTime now = DateTime.now(); - DateTime? start = getStartTime(); - - if (start != null) { - return start.isAfter(now); - } - return false; - } -} +class ClassScheduleModel { + final String id; + final Map> weeklySchedule; + + ClassScheduleModel({required this.id, required this.weeklySchedule}); + + factory ClassScheduleModel.fromJson(String id, Map json) { + Map> schedule = {}; + + // Parse each day of the week + json.forEach((day, timeSlots) { + if (timeSlots is Map) { + List daySlots = []; + + // Extract time slots and corresponding course details + timeSlots.forEach((key, value) { + if (key.startsWith('course_')) { + // Skip course keys, they are handled with time slots + return; + } + + if (value is String) { + // Find the corresponding course key (e.g., course_0 for 0) + String courseKey = 'course_$key'; + String courseName = timeSlots[courseKey] ?? ''; + + daySlots.add( + TimeSlot( + timeRange: value, + subject: courseName, + teacher: '', // Add teacher if available in the structure + room: '', // Add room if available in the structure + code: courseName, + ), + ); + } + }); + + schedule[day] = daySlots; + } + }); + + return ClassScheduleModel(id: id, weeklySchedule: schedule); + } + + Map toJson() { + Map result = {}; + + weeklySchedule.forEach((day, timeSlots) { + Map dayData = {}; + for (TimeSlot slot in timeSlots) { + dayData[slot.timeRange] = slot.toJson(); + } + result[day] = dayData; + }); + + return result; + } + + // Get classes for today + List getTodaysClasses() { + String today = _getCurrentDayName(); + return weeklySchedule[today] ?? []; + } + + // Get classes for tomorrow + List getTomorrowsClasses() { + String tomorrow = _getTomorrowDayName(); + return weeklySchedule[tomorrow] ?? []; + } + + String _getCurrentDayName() { + List weekdays = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ]; + return weekdays[DateTime.now().weekday % 7]; + } + + String _getTomorrowDayName() { + List weekdays = [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ]; + return weekdays[(DateTime.now().weekday + 1) % 7]; + } +} + +class TimeSlot { + final String timeRange; + final String subject; + final String teacher; + final String room; + final String code; + + TimeSlot({ + required this.timeRange, + required this.subject, + required this.teacher, + required this.room, + required this.code, + }); + + factory TimeSlot.fromJson(String timeRange, Map json) { + return TimeSlot( + timeRange: timeRange, + subject: json['subject'] ?? '', + teacher: json['teacher'] ?? '', + room: json['room'] ?? '', + code: json['code'] ?? '', + ); + } + + Map toJson() { + return {'subject': subject, 'teacher': teacher, 'room': room, 'code': code}; + } + + // Parse start and end time from time range (e.g., "8:00-8:50") + DateTime? getStartTime() { + try { + String startTimeStr; + if (timeRange.contains('-')) { + startTimeStr = timeRange.split('-')[0]; + } else { + startTimeStr = timeRange; // It's a single time + } + List timeParts = startTimeStr.split(':'); + int hour = int.parse(timeParts[0]); + int minute = int.parse(timeParts[1]); + + DateTime now = DateTime.now(); + return DateTime(now.year, now.month, now.day, hour, minute); + } catch (e) { + // Consider logging this error + return null; + } + } + + DateTime? getEndTime() { + try { + if (timeRange.contains('-')) { + String endTimeStr = timeRange.split('-')[1]; + List timeParts = endTimeStr.split(':'); + int hour = int.parse(timeParts[0]); + int minute = int.parse(timeParts[1]); + + DateTime now = DateTime.now(); + return DateTime(now.year, now.month, now.day, hour, minute); + } else { + // No end time specified, calculate as start_time + 1 hour (default duration) + DateTime? startTime = getStartTime(); + if (startTime != null) { + return startTime.add( + const Duration(hours: 1), + ); // Default 1 hour duration + } + return null; + } + } catch (e) { + // Consider logging this error + return null; + } + } + + bool isCurrentClass() { + DateTime now = DateTime.now(); + DateTime? start = getStartTime(); + DateTime? end = getEndTime(); + + if (start != null && end != null) { + return now.isAfter(start) && now.isBefore(end); + } + return false; + } + + bool isUpcoming() { + DateTime now = DateTime.now(); + DateTime? start = getStartTime(); + + if (start != null) { + return start.isAfter(now); + } + return false; + } +} diff --git a/lib/models/notice_model.dart b/lib/models/notice_model.dart index 1d73139..d2dac8e 100644 --- a/lib/models/notice_model.dart +++ b/lib/models/notice_model.dart @@ -1,32 +1,32 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; - -class NoticeModel { - final String title; - final String content; - final DateTime createdAt; - final DateTime updatedAt; - - NoticeModel({ - required this.title, - required this.content, - required this.createdAt, - required this.updatedAt, - }); - - factory NoticeModel.fromJson(Map json) { - return NoticeModel( - title: json['title'] as String, - content: json['content'] as String, - createdAt: (json['created_at'] as Timestamp).toDate(), - updatedAt: (json['updated_at'] as Timestamp).toDate(), - ); - } - Map toJson() { - return { - 'title': title, - 'content': content, - 'created_at': createdAt, - 'updated_at': updatedAt, - }; - } -} +import 'package:cloud_firestore/cloud_firestore.dart'; + +class NoticeModel { + final String title; + final String content; + final DateTime createdAt; + final DateTime updatedAt; + + NoticeModel({ + required this.title, + required this.content, + required this.createdAt, + required this.updatedAt, + }); + + factory NoticeModel.fromJson(Map json) { + return NoticeModel( + title: json['title'] as String, + content: json['content'] as String, + createdAt: (json['created_at'] as Timestamp).toDate(), + updatedAt: (json['updated_at'] as Timestamp).toDate(), + ); + } + Map toJson() { + return { + 'title': title, + 'content': content, + 'created_at': createdAt, + 'updated_at': updatedAt, + }; + } +} diff --git a/lib/models/notification_model.dart b/lib/models/notification_model.dart index 01b2b9f..b7c1da2 100644 --- a/lib/models/notification_model.dart +++ b/lib/models/notification_model.dart @@ -1,91 +1,91 @@ -import 'package:cloud_firestore/cloud_firestore.dart'; - -class NotificationModel { - final String id; - final String title; - final String message; - final String? imageUrl; - final String type; - final bool isRead; - final DateTime createdAt; - final Map? data; - - NotificationModel({ - required this.id, - required this.title, - required this.message, - this.imageUrl, - required this.type, - required this.isRead, - required this.createdAt, - this.data, - }); - - factory NotificationModel.fromFirestore(DocumentSnapshot doc) { - final data = doc.data() as Map; - return NotificationModel( - id: doc.id, - title: data['title'] as String? ?? '', - message: data['message'] as String? ?? '', - imageUrl: data['imageUrl'] as String?, - type: data['type'] as String? ?? 'general', - isRead: data['isRead'] as bool? ?? false, - createdAt: (data['createdAt'] as Timestamp?)?.toDate() ?? DateTime.now(), - data: data['data'] as Map?, - ); - } - - factory NotificationModel.fromJson(Map json) { - return NotificationModel( - id: json['id'] as String? ?? '', - title: json['title'] as String? ?? '', - message: json['content'] as String? ?? '', - imageUrl: json['imageUrl'] as String?, - type: json['type'] as String? ?? 'general', - isRead: json['isRead'] as bool? ?? false, - createdAt: - json['createdAt'] is Timestamp - ? (json['createdAt'] as Timestamp).toDate() - : DateTime.parse( - json['createdAt'] as String? ?? - DateTime.now().toIso8601String(), - ), - data: json['data'] as Map?, - ); - } - - Map toJson() { - return { - 'id': id, - 'title': title, - 'message': message, - 'imageUrl': imageUrl, - 'type': type, - 'isRead': isRead, - 'createdAt': createdAt, - 'data': data, - }; - } - - NotificationModel copyWith({ - String? id, - String? title, - String? message, - String? imageUrl, - String? type, - bool? isRead, - DateTime? createdAt, - Map? data, - }) { - return NotificationModel( - id: id ?? this.id, - title: title ?? this.title, - message: message ?? this.message, - imageUrl: imageUrl ?? this.imageUrl, - type: type ?? this.type, - isRead: isRead ?? this.isRead, - createdAt: createdAt ?? this.createdAt, - data: data ?? this.data, - ); - } -} +import 'package:cloud_firestore/cloud_firestore.dart'; + +class NotificationModel { + final String id; + final String title; + final String message; + final String? imageUrl; + final String type; + final bool isRead; + final DateTime createdAt; + final Map? data; + + NotificationModel({ + required this.id, + required this.title, + required this.message, + this.imageUrl, + required this.type, + required this.isRead, + required this.createdAt, + this.data, + }); + + factory NotificationModel.fromFirestore(DocumentSnapshot doc) { + final data = doc.data() as Map; + return NotificationModel( + id: doc.id, + title: data['title'] as String? ?? '', + message: data['message'] as String? ?? '', + imageUrl: data['imageUrl'] as String?, + type: data['type'] as String? ?? 'general', + isRead: data['isRead'] as bool? ?? false, + createdAt: (data['createdAt'] as Timestamp?)?.toDate() ?? DateTime.now(), + data: data['data'] as Map?, + ); + } + + factory NotificationModel.fromJson(Map json) { + return NotificationModel( + id: json['id'] as String? ?? '', + title: json['title'] as String? ?? '', + message: json['content'] as String? ?? '', + imageUrl: json['imageUrl'] as String?, + type: json['type'] as String? ?? 'general', + isRead: json['isRead'] as bool? ?? false, + createdAt: + json['createdAt'] is Timestamp + ? (json['createdAt'] as Timestamp).toDate() + : DateTime.parse( + json['createdAt'] as String? ?? + DateTime.now().toIso8601String(), + ), + data: json['data'] as Map?, + ); + } + + Map toJson() { + return { + 'id': id, + 'title': title, + 'message': message, + 'imageUrl': imageUrl, + 'type': type, + 'isRead': isRead, + 'createdAt': createdAt, + 'data': data, + }; + } + + NotificationModel copyWith({ + String? id, + String? title, + String? message, + String? imageUrl, + String? type, + bool? isRead, + DateTime? createdAt, + Map? data, + }) { + return NotificationModel( + id: id ?? this.id, + title: title ?? this.title, + message: message ?? this.message, + imageUrl: imageUrl ?? this.imageUrl, + type: type ?? this.type, + isRead: isRead ?? this.isRead, + createdAt: createdAt ?? this.createdAt, + data: data ?? this.data, + ); + } +} diff --git a/lib/models/user_model.dart b/lib/models/user_model.dart index 142b2f1..ef9adaf 100644 --- a/lib/models/user_model.dart +++ b/lib/models/user_model.dart @@ -1,98 +1,98 @@ -class UserModel { - final String uid; - final String name; - final String email; - final String faculty; - final String id; - final String regi; - final String phone; - final DateTime createdAt; - final DateTime updatedAt; - String? profileImageUrl; - - UserModel({ - required this.uid, - required this.name, - required this.email, - required this.faculty, - required this.id, - this.profileImageUrl, - required this.regi, - required this.phone, - required this.createdAt, - required this.updatedAt, - }); - - // Convert UserModel to Map for Firestore - Map toMap() { - return { - 'profileImageUrl': profileImageUrl, - 'uid': uid, - 'name': name, - 'email': email, - 'faculty': faculty, - 'id': id, - 'regi': regi, - 'phone': phone, - 'createdAt': createdAt.millisecondsSinceEpoch, - 'updatedAt': updatedAt.millisecondsSinceEpoch, - }; - } - - // Create UserModel from Firestore Map - factory UserModel.fromMap(Map map) { - return UserModel( - profileImageUrl: map['profileImageUrl'] as String?, - uid: map['uid'] ?? '', - name: map['name'] ?? '', - email: map['email'] ?? '', - faculty: map['faculty'] ?? '', - id: map['id'] ?? '', - regi: map['regi'] ?? '', - phone: map['phone'] ?? '', - createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt'] ?? 0), - updatedAt: DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] ?? 0), - ); - } - - // Create a copy with updated fields - UserModel copyWith({ - String? uid, - String? name, - String? email, - String? faculty, - String? id, - String? regi, - String? phone, - DateTime? createdAt, - DateTime? updatedAt, - }) { - return UserModel( - uid: uid ?? this.uid, - name: name ?? this.name, - email: email ?? this.email, - faculty: faculty ?? this.faculty, - id: id ?? this.id, - regi: regi ?? this.regi, - phone: phone ?? this.phone, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - ); - } - - @override - String toString() { - return 'UserModel(uid: $uid, name: $name, email: $email, faculty: $faculty, id: $id, regi: $regi, phone: $phone)'; - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is UserModel && other.uid == uid; - } - - @override - int get hashCode { - return uid.hashCode; - } -} +class UserModel { + final String uid; + final String name; + final String email; + final String faculty; + final String id; + final String regi; + final String phone; + final DateTime createdAt; + final DateTime updatedAt; + String? profileImageUrl; + + UserModel({ + required this.uid, + required this.name, + required this.email, + required this.faculty, + required this.id, + this.profileImageUrl, + required this.regi, + required this.phone, + required this.createdAt, + required this.updatedAt, + }); + + // Convert UserModel to Map for Firestore + Map toMap() { + return { + 'profileImageUrl': profileImageUrl, + 'uid': uid, + 'name': name, + 'email': email, + 'faculty': faculty, + 'id': id, + 'regi': regi, + 'phone': phone, + 'createdAt': createdAt.millisecondsSinceEpoch, + 'updatedAt': updatedAt.millisecondsSinceEpoch, + }; + } + + // Create UserModel from Firestore Map + factory UserModel.fromMap(Map map) { + return UserModel( + profileImageUrl: map['profileImageUrl'] as String?, + uid: map['uid'] ?? '', + name: map['name'] ?? '', + email: map['email'] ?? '', + faculty: map['faculty'] ?? '', + id: map['id'] ?? '', + regi: map['regi'] ?? '', + phone: map['phone'] ?? '', + createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt'] ?? 0), + updatedAt: DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] ?? 0), + ); + } + + // Create a copy with updated fields + UserModel copyWith({ + String? uid, + String? name, + String? email, + String? faculty, + String? id, + String? regi, + String? phone, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return UserModel( + uid: uid ?? this.uid, + name: name ?? this.name, + email: email ?? this.email, + faculty: faculty ?? this.faculty, + id: id ?? this.id, + regi: regi ?? this.regi, + phone: phone ?? this.phone, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + String toString() { + return 'UserModel(uid: $uid, name: $name, email: $email, faculty: $faculty, id: $id, regi: $regi, phone: $phone)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is UserModel && other.uid == uid; + } + + @override + int get hashCode { + return uid.hashCode; + } +} diff --git a/lib/services/email_service.dart b/lib/services/email_service.dart index d0beba7..09fd8a9 100644 --- a/lib/services/email_service.dart +++ b/lib/services/email_service.dart @@ -1,268 +1,269 @@ -import 'package:mailer/mailer.dart'; -import 'package:mailer/smtp_server.dart'; -import 'dart:io'; // Required for File - -class EmailService { - final String username = 'email'; - final String password = 'app_password'; - - // Method to send OTP email - Future sendOtpEmail(String receiverEmail, String otp) async { - final smtpServer = SmtpServer( - 'smtp.gmail.com', - port: 465, - ssl: true, - username: username, - password: password, - ); - - final message = - Message() - ..from = Address(username, 'MyEdu University') - ..recipients.add(receiverEmail) - ..subject = 'Your OTP Code for Account Verification' - ..html = ''' -
-
- -

Hi,

-

Thank you for choosing MyEdu University. Use the following OTP to complete your Sign Up procedures. OTP is valid for 5 minutes

-

$otp

-

Regards,
MyEdu University

-
-
-

MyEdu University

-

Bangladesh

-
-
-
- '''; - - try { - final sendReport = await send(message, smtpServer); - print('OTP Email sent: $sendReport'); - } on MailerException catch (e) { - print('OTP Email not sent. \n${e.toString()}'); - for (var p in e.problems) { - print('Problem: ${p.code}: ${p.msg}'); - } - rethrow; - } - } - - // Method to send invoice email with PDF attachment - Future sendInvoiceEmail(String receiverEmail, File pdfFile) async { - final smtpServer = SmtpServer( - 'smtp.gmail.com', - port: 465, - ssl: true, - username: username, - password: password, - ); - - // Create the email with PDF attachment - final message = - Message() - ..from = Address(username, 'MyEdu University') - ..recipients.add(receiverEmail) - ..subject = 'Payment Receipt from MyEdu University' - ..html = ''' -
-
- -

Hi,

-

Your payment to MyEdu University was successful. Please find your payment receipt attached as a PDF.

-

Regards,
MyEdu University

-
-
-

MyEdu University

-

Dhaka, Bangladesh

-
-
-
- ''' - ..attachments.add(FileAttachment(pdfFile)); // Attach PDF file - - try { - final sendReport = await send(message, smtpServer); - print('Invoice Email sent: $sendReport'); - } on MailerException catch (e) { - print('Invoice Email not sent. \n${e.toString()}'); - for (var p in e.problems) { - print('Problem: ${p.code}: ${p.msg}'); - } - rethrow; - } - } - - Future sendMarksheetEmail( - String receiverEmail, - File pdfFile, - String studentName, - ) async { - final smtpServer = SmtpServer( - 'smtp.gmail.com', - port: 465, - ssl: true, - username: username, - password: password, - ); - - // Create the email with PDF attachment - final message = - Message() - ..from = Address(username, 'MyEdu University') - ..recipients.add(receiverEmail) - ..subject = 'Your Academic Marksheet from MyEdu University' - ..html = ''' -
-
- -

Hi $studentName,

-

Your academic marksheet is attached as a PDF. Please review it and keep it for your records.

-

Regards,
MyEdu University

-
-
-

MyEdu University

-

Dhaka, Bangladesh

-
-
-
- ''' - ..attachments.add(FileAttachment(pdfFile)); // Attach PDF file - - try { - final sendReport = await send(message, smtpServer); - print('Marksheet Email sent: $sendReport'); - } on MailerException catch (e) { - print('Marksheet Email not sent. \n${e.toString()}'); - for (var p in e.problems) { - print('Problem: ${p.code}: ${p.msg}'); - } - rethrow; - } - } - - Future sendPaymentConfirmationEmail({ - required String receiverEmail, - required String studentName, - required String receiptNumber, - required double amount, - required String purpose, - required String academicYear, - required String semester, - required DateTime paymentDate, - required File receiptPdf, - required Map paymentData, - }) async { - final smtpServer = SmtpServer( - 'smtp.gmail.com', - port: 465, - ssl: true, - username: username, - password: password, - ); - - // Format payment date - final formattedDate = - '${paymentDate.day}/${paymentDate.month}/${paymentDate.year} at ${paymentDate.hour}:${paymentDate.minute.toString().padLeft(2, '0')}'; - - // Format amount - final formattedAmount = 'เงณ${amount.toStringAsFixed(2)}'; - - // Create the email with PDF attachment - final message = - Message() - ..from = Address(username, 'MyEdu University') - ..recipients.add(receiverEmail) - ..subject = 'Payment Confirmation - Receipt #$receiptNumber' - ..html = ''' -
-
- - -

Payment Confirmation

- -

Dear $studentName,

- -

We are pleased to confirm that your payment has been successfully processed. Please find the details below:

- -
-

Payment Details

- - - - - - - - - - - - - - - - - - - - - - - - - -
Receipt Number:$receiptNumber
Payment Date:$formattedDate
Purpose:$purpose
Academic Year:$academicYear
Semester:$semester
Amount Paid:$formattedAmount
-
- -
-

โœ“ Payment Status: Successfully Completed

-
- -

Your official receipt is attached to this email as a PDF document. Please keep this receipt for your records.

- -
-

Important Notes:

-
    -
  • This receipt serves as proof of payment
  • -
  • Please retain this document for future reference
  • -
  • For any queries, contact the accounts office
  • -
-
- -

Thank you for your payment.
Regards,
Accounts Department
MyEdu University

- -
-
-

MyEdu University

-

Dhaka, Bangladesh

-

Phone: +880-4427-56072

-

Email: accounts@myedu.edu.bd

-
-
-
- ''' - ..attachments.add(FileAttachment(receiptPdf)); // Attach PDF receipt - - try { - final sendReport = await send(message, smtpServer); - print('Payment Confirmation Email sent: $sendReport'); - } on MailerException catch (e) { - print('Payment Confirmation Email not sent. \n${e.toString()}'); - for (var p in e.problems) { - print('Problem: ${p.code}: ${p.msg}'); - } - rethrow; - } - } -} +import 'dart:io'; // Required for File + +import 'package:mailer/mailer.dart'; +import 'package:mailer/smtp_server.dart'; + +class EmailService { + final String username = 'shahariapro@gmail.com'; + final String password = 'rcwqmeggxjdnegkf'; + + // Method to send OTP email + Future sendOtpEmail(String receiverEmail, String otp) async { + final smtpServer = SmtpServer( + 'smtp.gmail.com', + port: 465, + ssl: true, + username: username, + password: password, + ); + + final message = + Message() + ..from = Address(username, 'MyEdu University') + ..recipients.add(receiverEmail) + ..subject = 'Your OTP Code for Account Verification' + ..html = ''' +
+
+ +

Hi,

+

Thank you for choosing MyEdu University. Use the following OTP to complete your Sign Up procedures. OTP is valid for 5 minutes

+

$otp

+

Regards,
MyEdu University

+
+
+

MyEdu University

+

Bangladesh

+
+
+
+ '''; + + try { + final sendReport = await send(message, smtpServer); + print('OTP Email sent: $sendReport'); + } on MailerException catch (e) { + print('OTP Email not sent. \n${e.toString()}'); + for (var p in e.problems) { + print('Problem: ${p.code}: ${p.msg}'); + } + rethrow; + } + } + + // Method to send invoice email with PDF attachment + Future sendInvoiceEmail(String receiverEmail, File pdfFile) async { + final smtpServer = SmtpServer( + 'smtp.gmail.com', + port: 465, + ssl: true, + username: username, + password: password, + ); + + // Create the email with PDF attachment + final message = + Message() + ..from = Address(username, 'MyEdu University') + ..recipients.add(receiverEmail) + ..subject = 'Payment Receipt from MyEdu University' + ..html = ''' +
+
+ +

Hi,

+

Your payment to MyEdu University was successful. Please find your payment receipt attached as a PDF.

+

Regards,
MyEdu University

+
+
+

MyEdu University

+

Dhaka, Bangladesh

+
+
+
+ ''' + ..attachments.add(FileAttachment(pdfFile)); // Attach PDF file + + try { + final sendReport = await send(message, smtpServer); + print('Invoice Email sent: $sendReport'); + } on MailerException catch (e) { + print('Invoice Email not sent. \n${e.toString()}'); + for (var p in e.problems) { + print('Problem: ${p.code}: ${p.msg}'); + } + rethrow; + } + } + + Future sendMarksheetEmail( + String receiverEmail, + File pdfFile, + String studentName, + ) async { + final smtpServer = SmtpServer( + 'smtp.gmail.com', + port: 465, + ssl: true, + username: username, + password: password, + ); + + // Create the email with PDF attachment + final message = + Message() + ..from = Address(username, 'MyEdu University') + ..recipients.add(receiverEmail) + ..subject = 'Your Academic Marksheet from MyEdu University' + ..html = ''' +
+
+ +

Hi $studentName,

+

Your academic marksheet is attached as a PDF. Please review it and keep it for your records.

+

Regards,
MyEdu University

+
+
+

MyEdu University

+

Dhaka, Bangladesh

+
+
+
+ ''' + ..attachments.add(FileAttachment(pdfFile)); // Attach PDF file + + try { + final sendReport = await send(message, smtpServer); + print('Marksheet Email sent: $sendReport'); + } on MailerException catch (e) { + print('Marksheet Email not sent. \n${e.toString()}'); + for (var p in e.problems) { + print('Problem: ${p.code}: ${p.msg}'); + } + rethrow; + } + } + + Future sendPaymentConfirmationEmail({ + required String receiverEmail, + required String studentName, + required String receiptNumber, + required double amount, + required String purpose, + required String academicYear, + required String semester, + required DateTime paymentDate, + required File receiptPdf, + required Map paymentData, + }) async { + final smtpServer = SmtpServer( + 'smtp.gmail.com', + port: 465, + ssl: true, + username: username, + password: password, + ); + + // Format payment date + final formattedDate = + '${paymentDate.day}/${paymentDate.month}/${paymentDate.year} at ${paymentDate.hour}:${paymentDate.minute.toString().padLeft(2, '0')}'; + + // Format amount + final formattedAmount = 'เงณ${amount.toStringAsFixed(2)}'; + + // Create the email with PDF attachment + final message = + Message() + ..from = Address(username, 'MyEdu University') + ..recipients.add(receiverEmail) + ..subject = 'Payment Confirmation - Receipt #$receiptNumber' + ..html = ''' +
+
+ + +

Payment Confirmation

+ +

Dear $studentName,

+ +

We are pleased to confirm that your payment has been successfully processed. Please find the details below:

+ +
+

Payment Details

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Receipt Number:$receiptNumber
Payment Date:$formattedDate
Purpose:$purpose
Academic Year:$academicYear
Semester:$semester
Amount Paid:$formattedAmount
+
+ +
+

โœ“ Payment Status: Successfully Completed

+
+ +

Your official receipt is attached to this email as a PDF document. Please keep this receipt for your records.

+ +
+

Important Notes:

+
    +
  • This receipt serves as proof of payment
  • +
  • Please retain this document for future reference
  • +
  • For any queries, contact the accounts office
  • +
+
+ +

Thank you for your payment.
Regards,
Accounts Department
MyEdu University

+ +
+
+

MyEdu University

+

Dhaka, Bangladesh

+

Phone: +880-4427-56072

+

Email: accounts@myedu.edu.bd

+
+
+
+ ''' + ..attachments.add(FileAttachment(receiptPdf)); // Attach PDF receipt + + try { + final sendReport = await send(message, smtpServer); + print('Payment Confirmation Email sent: $sendReport'); + } on MailerException catch (e) { + print('Payment Confirmation Email not sent. \n${e.toString()}'); + for (var p in e.problems) { + print('Problem: ${p.code}: ${p.msg}'); + } + rethrow; + } + } +} diff --git a/lib/services/pdf_marksheet_service.dart b/lib/services/pdf_marksheet_service.dart index 8806581..2283b48 100644 --- a/lib/services/pdf_marksheet_service.dart +++ b/lib/services/pdf_marksheet_service.dart @@ -15,6 +15,7 @@ class PdfMarksheetService { UserModel user, ) async { final pdf = pw.Document(); + final sortedResults = _sortResults(results); pdf.addPage( pw.MultiPage( @@ -24,21 +25,19 @@ class PdfMarksheetService { (context) => [ _buildProfessionalHeader(user), _buildStudentDetailsSection(user), - _buildAcademicSummarySection(results), - _buildDetailedResultsSection(results), - _buildGradeAnalysisSection(results), + _buildAcademicSummarySection(sortedResults), + _buildDetailedResultsSection(sortedResults), + _buildGradeAnalysisSection(sortedResults), _buildFooter(), ], ), ); - final output = await getTemporaryDirectory(); - final file = File( - '${output.path}/comprehensive_marksheet_${user.name.replaceAll(' ', '_')}.pdf', + final file = await _savePdf( + pdf, + 'comprehensive_marksheet_${user.id}_${user.name}', ); - await file.writeAsBytes(await pdf.save()); - - await OpenFile.open(file.path); + await _openPdf(file); } static Future generateSemesterMarksheet( @@ -63,13 +62,11 @@ class PdfMarksheetService { ), ); - final output = await getTemporaryDirectory(); - final file = File( - '${output.path}/semester_marksheet_${result.semester}_${result.academicYear}.pdf', + final file = await _savePdf( + pdf, + 'semester_marksheet_${user.id}_${result.semester}_${result.academicYear}', ); - await file.writeAsBytes(await pdf.save()); - - await OpenFile.open(file.path); + await _openPdf(file); } // New method to generate and email comprehensive marksheet @@ -78,6 +75,7 @@ class PdfMarksheetService { UserModel user, ) async { final pdf = pw.Document(); + final sortedResults = _sortResults(results); pdf.addPage( pw.MultiPage( @@ -87,19 +85,18 @@ class PdfMarksheetService { (context) => [ _buildProfessionalHeader(user), _buildStudentDetailsSection(user), - _buildAcademicSummarySection(results), - _buildDetailedResultsSection(results), - _buildGradeAnalysisSection(results), + _buildAcademicSummarySection(sortedResults), + _buildDetailedResultsSection(sortedResults), + _buildGradeAnalysisSection(sortedResults), _buildFooter(), ], ), ); - final output = await getTemporaryDirectory(); - final file = File( - '${output.path}/comprehensive_marksheet_${user.name.replaceAll(' ', '_')}.pdf', + final file = await _savePdf( + pdf, + 'comprehensive_marksheet_${user.id}_${user.name}', ); - await file.writeAsBytes(await pdf.save()); // Send email with PDF attachment await _emailService.sendMarksheetEmail(user.email, file, user.name); @@ -128,11 +125,10 @@ class PdfMarksheetService { ), ); - final output = await getTemporaryDirectory(); - final file = File( - '${output.path}/semester_marksheet_${result.semester}_${result.academicYear}.pdf', + final file = await _savePdf( + pdf, + 'semester_marksheet_${user.id}_${result.semester}_${result.academicYear}', ); - await file.writeAsBytes(await pdf.save()); // Send email with PDF attachment await _emailService.sendMarksheetEmail(user.email, file, user.name); @@ -310,7 +306,7 @@ class PdfMarksheetService { _buildSummaryCard( 'Total Credits', totalCredits.toStringAsFixed(1), - '${(results.length * 15).toStringAsFixed(1)}', + (results.length * 15).toStringAsFixed(1), PdfColors.blue, ), _buildSummaryCard( @@ -322,7 +318,7 @@ class PdfMarksheetService { _buildSummaryCard( 'Semesters', results.length.toString(), - '${results.length}', + results.length.toString(), PdfColors.orange, ), ], @@ -351,7 +347,7 @@ class PdfMarksheetService { ), ), pw.SizedBox(height: 15), - ...results.map((result) => _buildSemesterResultCard(result)).toList(), + ...results.map((result) => _buildSemesterResultCard(result)), ], ), ); @@ -481,22 +477,20 @@ class PdfMarksheetService { ], ), // Data rows - ...result.courses - .map( - (course) => pw.TableRow( - children: [ - _buildTableCell(course.courseId, isBold: true), - _buildTableCell(course.courseName), - _buildTableCell(course.credit), - _buildTableCell( - course.grade, - color: _getGradeColor(course.grade), - ), - _buildTableCell(course.gpa), - ], + ...result.courses.map( + (course) => pw.TableRow( + children: [ + _buildTableCell(course.courseId, isBold: true), + _buildTableCell(course.courseName), + _buildTableCell(course.credit), + _buildTableCell( + course.grade, + color: _getGradeColor(course.grade), ), - ) - .toList(), + _buildTableCell(course.gpa), + ], + ), + ), ], ); } @@ -534,8 +528,10 @@ class PdfMarksheetService { ), ), pw.SizedBox(height: 15), - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceAround, + pw.Wrap( + spacing: 10, + runSpacing: 10, + alignment: pw.WrapAlignment.center, children: overallDistribution.entries .map( @@ -570,8 +566,10 @@ class PdfMarksheetService { ), ), pw.SizedBox(height: 15), - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceAround, + pw.Wrap( + spacing: 10, + runSpacing: 10, + alignment: pw.WrapAlignment.center, children: result.gradeDistribution.entries .map( @@ -843,6 +841,47 @@ class PdfMarksheetService { return PdfColors.red; } + static Future _savePdf(pw.Document pdf, String fileName) async { + final output = await getApplicationDocumentsDirectory(); + final safeName = _safeFileName(fileName); + final file = File('${output.path}/$safeName.pdf'); + await file.writeAsBytes(await pdf.save(), flush: true); + return file; + } + + static Future _openPdf(File file) async { + final result = await OpenFile.open(file.path); + if (result.type != ResultType.done) { + throw Exception(result.message); + } + } + + static List _sortResults( + List results, + ) { + final sorted = [...results]; + sorted.sort((a, b) { + final yearCompare = a.academicYear.compareTo(b.academicYear); + if (yearCompare != 0) return yearCompare; + return _semesterOrder(a.semester).compareTo(_semesterOrder(b.semester)); + }); + return sorted; + } + + static int _semesterOrder(String semester) { + final match = RegExp(r'\d+').firstMatch(semester); + return int.tryParse(match?.group(0) ?? '') ?? 999; + } + + static String _safeFileName(String value) { + final sanitized = value + .trim() + .replaceAll(RegExp(r'[^A-Za-z0-9._-]+'), '_') + .replaceAll(RegExp(r'_+'), '_') + .replaceAll(RegExp(r'^_+|_+$'), ''); + return sanitized.isEmpty ? 'marksheet' : sanitized; + } + static String _getCurrentDate() { final now = DateTime.now(); return '${now.day.toString().padLeft(2, '0')}/${now.month.toString().padLeft(2, '0')}/${now.year}'; diff --git a/lib/services/pdf_receipt_service.dart b/lib/services/pdf_receipt_service.dart index 2178614..bafb263 100644 --- a/lib/services/pdf_receipt_service.dart +++ b/lib/services/pdf_receipt_service.dart @@ -1,805 +1,805 @@ -import 'dart:io'; -import 'package:pdf/pdf.dart'; -import 'package:pdf/widgets.dart' as pw; -import 'package:http/http.dart' as http; -import 'package:printing/printing.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:open_file/open_file.dart'; -import 'package:intl/intl.dart'; -import '../models/academic_fee_model.dart'; -import '../models/user_model.dart'; -import 'email_service.dart'; - -class PdfReceiptService { - static final EmailService _emailService = EmailService(); - - static Future generateAndShowReceipt({ - required AcademicFeeModel fee, - required UserModel user, - }) async { - final pdf = pw.Document(); - - // Load custom font if available - final font = await PdfGoogleFonts.notoSansRegular(); - final fontBold = await PdfGoogleFonts.notoSansBold(); - - pdf.addPage( - pw.Page( - pageFormat: PdfPageFormat.a4, - margin: const pw.EdgeInsets.all(40), - build: (pw.Context context) { - return pw.Stack( - children: [ - // Main content - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // Header with University Name - _buildHeader(fontBold, font), - pw.SizedBox(height: 30), - - // Receipt Title and Number - _buildReceiptTitle(fee, fontBold, font), - pw.SizedBox(height: 25), - - // Student Information - _buildStudentInfo(user, fontBold, font), - pw.SizedBox(height: 25), - - // Payment Details - _buildPaymentDetails(fee, fontBold, font), - pw.SizedBox(height: 25), - - // Fee Items Table - _buildFeeItemsTable(fee, fontBold, font), - pw.SizedBox(height: 25), - - // Total and Payment Info - _buildTotalSection(fee, fontBold, font), - pw.SizedBox(height: 30), - - // Footer - _buildFooter(font), - ], - ), - // Watermark - _buildWatermark(), - ], - ); - }, - ), - ); - - // Save and open the PDF file - try { - final directory = await getApplicationDocumentsDirectory(); - final file = File( - '${directory.path}/fee_receipt_${fee.receiptNumber}.pdf', - ); - await file.writeAsBytes(await pdf.save()); - - // Open the PDF file - await OpenFile.open(file.path); - } catch (e) { - // Fallback to print preview if opening fails - await Printing.layoutPdf( - onLayout: (PdfPageFormat format) async => pdf.save(), - name: 'Fee_Receipt_${fee.receiptNumber}', - ); - } - } - - static pw.Widget _buildHeader(pw.Font fontBold, pw.Font font) { - return pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.symmetric(vertical: 20), - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.blue800, width: 2), - borderRadius: const pw.BorderRadius.all(pw.Radius.circular(10)), - gradient: const pw.LinearGradient( - colors: [PdfColors.blue50, PdfColors.white], - begin: pw.Alignment.topCenter, - end: pw.Alignment.bottomCenter, - ), - ), - child: pw.Column( - children: [ - pw.Text( - 'Patuakhali Science and Technology University', - style: pw.TextStyle( - font: fontBold, - fontSize: 24, - color: PdfColors.blue900, - letterSpacing: 1.2, - ), - textAlign: pw.TextAlign.center, - ), - pw.SizedBox(height: 8), - pw.Text( - 'Dumki, Patuakhali-8602, Bangladesh', - style: pw.TextStyle( - font: font, - fontSize: 14, - color: PdfColors.blue700, - ), - textAlign: pw.TextAlign.center, - ), - pw.SizedBox(height: 4), - pw.Text( - 'Phone: +880-4427-56072, Email: info@pstu.ac.bd', - style: pw.TextStyle( - font: font, - fontSize: 12, - color: PdfColors.blue600, - ), - textAlign: pw.TextAlign.center, - ), - ], - ), - ); - } - - static pw.Widget _buildReceiptTitle( - AcademicFeeModel fee, - pw.Font fontBold, - pw.Font font, - ) { - return pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(15), - decoration: pw.BoxDecoration( - color: PdfColors.blue100, - borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), - ), - child: pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'PAYMENT RECEIPT', - style: pw.TextStyle( - font: fontBold, - fontSize: 20, - color: PdfColors.blue900, - ), - ), - pw.SizedBox(height: 4), - pw.Text( - 'Academic Fee Payment', - style: pw.TextStyle( - font: font, - fontSize: 14, - color: PdfColors.blue700, - ), - ), - ], - ), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - pw.Text( - 'Receipt No.', - style: pw.TextStyle( - font: font, - fontSize: 12, - color: PdfColors.blue600, - ), - ), - pw.Text( - fee.receiptNumber ?? 'N/A', - style: pw.TextStyle( - font: fontBold, - fontSize: 16, - color: PdfColors.blue900, - ), - ), - pw.SizedBox(height: 8), - pw.Text( - 'Date: ${DateFormat('MMM dd, yyyy').format(fee.paidAt ?? DateTime.now())}', - style: pw.TextStyle( - font: font, - fontSize: 12, - color: PdfColors.blue700, - ), - ), - ], - ), - ], - ), - ); - } - - static pw.Widget _buildStudentInfo( - UserModel user, - pw.Font fontBold, - pw.Font font, - ) { - return pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(15), - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.grey300), - borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), - ), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'STUDENT INFORMATION', - style: pw.TextStyle( - font: fontBold, - fontSize: 16, - color: PdfColors.blue900, - ), - ), - pw.SizedBox(height: 12), - pw.Row( - children: [ - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - _buildInfoRow('Name', user.name, fontBold, font), - _buildInfoRow('Student ID', user.id, fontBold, font), - _buildInfoRow('Registration', user.regi, fontBold, font), - ], - ), - ), - pw.SizedBox(width: 20), - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - _buildInfoRow('Faculty', user.faculty, fontBold, font), - _buildInfoRow('Email', user.email, fontBold, font), - _buildInfoRow('Phone', user.phone, fontBold, font), - ], - ), - ), - ], - ), - ], - ), - ); - } - - static pw.Widget _buildInfoRow( - String label, - String value, - pw.Font fontBold, - pw.Font font, - ) { - return pw.Padding( - padding: const pw.EdgeInsets.only(bottom: 8), - child: pw.Row( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.SizedBox( - width: 80, - child: pw.Text( - '$label:', - style: pw.TextStyle( - font: fontBold, - fontSize: 12, - color: PdfColors.grey700, - ), - ), - ), - pw.Expanded( - child: pw.Text( - value, - style: pw.TextStyle( - font: font, - fontSize: 12, - color: PdfColors.black, - ), - ), - ), - ], - ), - ); - } - - static pw.Widget _buildPaymentDetails( - AcademicFeeModel fee, - pw.Font fontBold, - pw.Font font, - ) { - return pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(15), - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.grey300), - borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), - ), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'PAYMENT DETAILS', - style: pw.TextStyle( - font: fontBold, - fontSize: 16, - color: PdfColors.blue900, - ), - ), - pw.SizedBox(height: 12), - pw.Row( - children: [ - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - _buildInfoRow('Purpose', fee.purpose, fontBold, font), - _buildInfoRow( - 'Academic Year', - fee.academicYear, - fontBold, - font, - ), - _buildInfoRow('Semester', fee.semester, fontBold, font), - ], - ), - ), - pw.SizedBox(width: 20), - pw.Expanded( - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - _buildInfoRow( - 'Due Date', - DateFormat('MMM dd, yyyy').format(fee.deadline), - fontBold, - font, - ), - _buildInfoRow( - 'Payment Date', - DateFormat('MMM dd, yyyy').format(fee.paidAt!), - fontBold, - font, - ), - _buildInfoRow('Status', 'PAID', fontBold, font), - ], - ), - ), - ], - ), - if (fee.description != null) ...[ - pw.SizedBox(height: 8), - _buildInfoRow('Description', fee.description!, fontBold, font), - ], - ], - ), - ); - } - - static pw.Widget _buildFeeItemsTable( - AcademicFeeModel fee, - pw.Font fontBold, - pw.Font font, - ) { - if (fee.items.isEmpty) { - return pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(15), - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.grey300), - borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), - ), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'FEE BREAKDOWN', - style: pw.TextStyle( - font: fontBold, - fontSize: 16, - color: PdfColors.blue900, - ), - ), - pw.SizedBox(height: 12), - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Text( - fee.purpose, - style: pw.TextStyle( - font: font, - fontSize: 14, - color: PdfColors.black, - ), - ), - pw.Text( - 'เงณ${fee.amount.toStringAsFixed(2)}', - style: pw.TextStyle( - font: fontBold, - fontSize: 14, - color: PdfColors.black, - ), - ), - ], - ), - ], - ), - ); - } - - return pw.Container( - width: double.infinity, - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.grey300), - borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), - ), - child: pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Container( - width: double.infinity, - padding: const pw.EdgeInsets.all(15), - decoration: const pw.BoxDecoration( - color: PdfColors.blue100, - borderRadius: pw.BorderRadius.only( - topLeft: pw.Radius.circular(8), - topRight: pw.Radius.circular(8), - ), - ), - child: pw.Text( - 'FEE BREAKDOWN', - style: pw.TextStyle( - font: fontBold, - fontSize: 16, - color: PdfColors.blue900, - ), - ), - ), - pw.Table( - border: pw.TableBorder.all(color: PdfColors.grey300, width: 0.5), - children: [ - // Header row - pw.TableRow( - decoration: const pw.BoxDecoration(color: PdfColors.grey100), - children: [ - _buildTableCell('Description', fontBold, isHeader: true), - _buildTableCell('Amount (เงณ)', fontBold, isHeader: true), - ], - ), - // Data rows - ...fee.items.map( - (item) => pw.TableRow( - children: [ - _buildTableCell( - '${item.name}${item.description != null ? '\n${item.description}' : ''}', - font, - ), - _buildTableCell(item.amount.toStringAsFixed(2), font), - ], - ), - ), - ], - ), - ], - ), - ); - } - - static pw.Widget _buildTableCell( - String text, - pw.Font font, { - bool isHeader = false, - }) { - return pw.Container( - padding: const pw.EdgeInsets.all(10), - child: pw.Text( - text, - style: pw.TextStyle( - font: font, - fontSize: isHeader ? 12 : 11, - color: isHeader ? PdfColors.blue900 : PdfColors.black, - ), - ), - ); - } - - static pw.Widget _buildTotalSection( - AcademicFeeModel fee, - pw.Font fontBold, - pw.Font font, - ) { - return pw.Container( - width: double.infinity, - decoration: pw.BoxDecoration( - border: pw.Border.all(color: PdfColors.blue300, width: 2), - borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), - gradient: const pw.LinearGradient( - colors: [PdfColors.blue50, PdfColors.white], - begin: pw.Alignment.topCenter, - end: pw.Alignment.bottomCenter, - ), - ), - child: pw.Column( - children: [ - if (fee.lateFee != null && fee.lateFee! > 0) ...[ - _buildTotalRow('Subtotal', fee.amount, fontBold, font, false), - _buildTotalRow('Late Fee', fee.lateFee!, fontBold, font, false), - ], - if (fee.discount != null && fee.discount! > 0) - _buildTotalRow('Discount', -fee.discount!, fontBold, font, false), - _buildTotalRow('Total Amount', fee.totalAmount, fontBold, font, true), - ], - ), - ); - } - - static pw.Widget _buildTotalRow( - String label, - double amount, - pw.Font fontBold, - pw.Font font, - bool isTotal, - ) { - return pw.Container( - padding: const pw.EdgeInsets.symmetric(horizontal: 20, vertical: 12), - decoration: - isTotal - ? const pw.BoxDecoration( - color: PdfColors.blue900, - borderRadius: pw.BorderRadius.all(pw.Radius.circular(6)), - ) - : null, - child: pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Text( - label, - style: pw.TextStyle( - font: isTotal ? fontBold : font, - fontSize: isTotal ? 16 : 14, - color: isTotal ? PdfColors.white : PdfColors.blue900, - ), - ), - pw.Text( - 'เงณ${amount.toStringAsFixed(2)}', - style: pw.TextStyle( - font: fontBold, - fontSize: isTotal ? 18 : 14, - color: isTotal ? PdfColors.white : PdfColors.blue900, - ), - ), - ], - ), - ); - } - - static pw.Widget _buildFooter(pw.Font font) { - return pw.Column( - children: [ - pw.Divider(color: PdfColors.grey400), - pw.SizedBox(height: 15), - pw.Row( - mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, - children: [ - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - pw.Text( - 'Generated on: ${DateFormat('MMM dd, yyyy - hh:mm a').format(DateTime.now())}', - style: pw.TextStyle( - font: font, - fontSize: 10, - color: PdfColors.grey600, - ), - ), - pw.SizedBox(height: 4), - pw.Text( - 'This is a computer generated receipt.', - style: pw.TextStyle( - font: font, - fontSize: 10, - color: PdfColors.grey600, - ), - ), - ], - ), - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.end, - children: [ - pw.Text( - 'For queries, contact:', - style: pw.TextStyle( - font: font, - fontSize: 10, - color: PdfColors.grey600, - ), - ), - pw.Text( - 'accounts@pstu.ac.bd', - style: pw.TextStyle( - font: font, - fontSize: 10, - color: PdfColors.blue700, - ), - ), - ], - ), - ], - ), - ], - ); - } - - static pw.Widget _buildWatermark() { - return pw.Positioned.fill( - child: pw.Center( - child: pw.Opacity( - opacity: 0.1, // Simulate transparency - child: pw.Transform.rotate( - angle: 0.5, // ~-30 degrees - child: pw.Text( - 'PAID', - style: pw.TextStyle( - fontSize: 200, - color: PdfColors.red, - fontWeight: pw.FontWeight.bold, - letterSpacing: 5, - ), - ), - ), - ), - ), - ); - } - - static Future saveReceiptPdf({ - required AcademicFeeModel fee, - required UserModel user, - }) async { - final pdf = pw.Document(); - final font = await PdfGoogleFonts.notoSansRegular(); - final fontBold = await PdfGoogleFonts.notoSansBold(); - - pdf.addPage( - pw.Page( - pageFormat: PdfPageFormat.a4, - margin: const pw.EdgeInsets.all(40), - build: (pw.Context context) { - return pw.Stack( - children: [ - // Main content - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - _buildHeader(fontBold, font), - pw.SizedBox(height: 30), - _buildReceiptTitle(fee, fontBold, font), - pw.SizedBox(height: 25), - _buildStudentInfo(user, fontBold, font), - pw.SizedBox(height: 25), - _buildPaymentDetails(fee, fontBold, font), - pw.SizedBox(height: 25), - _buildFeeItemsTable(fee, fontBold, font), - pw.SizedBox(height: 25), - _buildTotalSection(fee, fontBold, font), - pw.SizedBox(height: 30), - _buildFooter(font), - ], - ), - // Watermark - _buildWatermark(), - ], - ); - }, - ), - ); - - final directory = await getApplicationDocumentsDirectory(); - final file = File('${directory.path}/receipt_${fee.receiptNumber}.pdf'); - await file.writeAsBytes(await pdf.save()); - - return file; - } - - // New method to generate and email receipt - static Future generateAndEmailReceipt({ - required AcademicFeeModel fee, - required UserModel user, - }) async { - final pdf = pw.Document(); - - // Load custom font if available - final font = await PdfGoogleFonts.notoSansRegular(); - final fontBold = await PdfGoogleFonts.notoSansBold(); - - pdf.addPage( - pw.Page( - pageFormat: PdfPageFormat.a4, - margin: const pw.EdgeInsets.all(40), - build: (pw.Context context) { - return pw.Stack( - children: [ - // Main content - pw.Column( - crossAxisAlignment: pw.CrossAxisAlignment.start, - children: [ - // Header with University Name - _buildHeader(fontBold, font), - pw.SizedBox(height: 30), - - // Receipt Title and Number - _buildReceiptTitle(fee, fontBold, font), - pw.SizedBox(height: 25), - - // Student Information - _buildStudentInfo(user, fontBold, font), - pw.SizedBox(height: 25), - - // Payment Details - _buildPaymentDetails(fee, fontBold, font), - pw.SizedBox(height: 25), - - // Fee Items Table - _buildFeeItemsTable(fee, fontBold, font), - pw.SizedBox(height: 25), - - // Total and Payment Info - _buildTotalSection(fee, fontBold, font), - pw.SizedBox(height: 30), - - // Footer - _buildFooter(font), - ], - ), - // Watermark - _buildWatermark(), - ], - ); - }, - ), - ); - - // Save PDF to file - final directory = await getApplicationDocumentsDirectory(); - final file = File('${directory.path}/receipt_${fee.receiptNumber}.pdf'); - await file.writeAsBytes(await pdf.save()); - - // Send email with receipt - try { - await _emailService.sendPaymentConfirmationEmail( - receiverEmail: user.email, - studentName: user.name, - receiptNumber: fee.receiptNumber ?? 'N/A', - amount: fee.totalAmount, - purpose: fee.purpose, - academicYear: fee.academicYear, - semester: fee.semester, - paymentDate: fee.paidAt ?? DateTime.now(), - receiptPdf: file, - paymentData: { - 'receipt_number': fee.receiptNumber, - 'amount': fee.totalAmount, - 'purpose': fee.purpose, - }, - ); - - // Format SMS message - final message = Uri.encodeComponent( - 'Payment Confirmation: BDT ${fee.totalAmount.toStringAsFixed(2)} ' - 'has been received for ${fee.purpose}. ' - 'Receipt ID #${fee.receiptNumber ?? 'N/A'}. Thank you.\n' - '-MyEdu (PSTU)', - ); - - // Send SMS notification - final smsUrl = - 'http://bulksmsbd.net/api/smsapi?api_key=YOUR_API_KEY' - '&type=text&number=${user.phone}&senderid=SENDER_ID&message=$message'; - - await http.get(Uri.parse(smsUrl)); - print('SMS sent successfully to ${user.phone}'); - } catch (e) { - print('Failed to send payment confirmation email: $e'); - rethrow; - } - } -} +import 'dart:io'; +import 'package:pdf/pdf.dart'; +import 'package:pdf/widgets.dart' as pw; +import 'package:http/http.dart' as http; +import 'package:printing/printing.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:open_file/open_file.dart'; +import 'package:intl/intl.dart'; +import '../models/academic_fee_model.dart'; +import '../models/user_model.dart'; +import 'email_service.dart'; + +class PdfReceiptService { + static final EmailService _emailService = EmailService(); + + static Future generateAndShowReceipt({ + required AcademicFeeModel fee, + required UserModel user, + }) async { + final pdf = pw.Document(); + + // Load custom font if available + final font = await PdfGoogleFonts.notoSansRegular(); + final fontBold = await PdfGoogleFonts.notoSansBold(); + + pdf.addPage( + pw.Page( + pageFormat: PdfPageFormat.a4, + margin: const pw.EdgeInsets.all(40), + build: (pw.Context context) { + return pw.Stack( + children: [ + // Main content + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // Header with University Name + _buildHeader(fontBold, font), + pw.SizedBox(height: 30), + + // Receipt Title and Number + _buildReceiptTitle(fee, fontBold, font), + pw.SizedBox(height: 25), + + // Student Information + _buildStudentInfo(user, fontBold, font), + pw.SizedBox(height: 25), + + // Payment Details + _buildPaymentDetails(fee, fontBold, font), + pw.SizedBox(height: 25), + + // Fee Items Table + _buildFeeItemsTable(fee, fontBold, font), + pw.SizedBox(height: 25), + + // Total and Payment Info + _buildTotalSection(fee, fontBold, font), + pw.SizedBox(height: 30), + + // Footer + _buildFooter(font), + ], + ), + // Watermark + _buildWatermark(), + ], + ); + }, + ), + ); + + // Save and open the PDF file + try { + final directory = await getApplicationDocumentsDirectory(); + final file = File( + '${directory.path}/fee_receipt_${fee.receiptNumber}.pdf', + ); + await file.writeAsBytes(await pdf.save()); + + // Open the PDF file + await OpenFile.open(file.path); + } catch (e) { + // Fallback to print preview if opening fails + await Printing.layoutPdf( + onLayout: (PdfPageFormat format) async => pdf.save(), + name: 'Fee_Receipt_${fee.receiptNumber}', + ); + } + } + + static pw.Widget _buildHeader(pw.Font fontBold, pw.Font font) { + return pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.symmetric(vertical: 20), + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.blue800, width: 2), + borderRadius: const pw.BorderRadius.all(pw.Radius.circular(10)), + gradient: const pw.LinearGradient( + colors: [PdfColors.blue50, PdfColors.white], + begin: pw.Alignment.topCenter, + end: pw.Alignment.bottomCenter, + ), + ), + child: pw.Column( + children: [ + pw.Text( + 'Patuakhali Science and Technology University', + style: pw.TextStyle( + font: fontBold, + fontSize: 24, + color: PdfColors.blue900, + letterSpacing: 1.2, + ), + textAlign: pw.TextAlign.center, + ), + pw.SizedBox(height: 8), + pw.Text( + 'Dumki, Patuakhali-8602, Bangladesh', + style: pw.TextStyle( + font: font, + fontSize: 14, + color: PdfColors.blue700, + ), + textAlign: pw.TextAlign.center, + ), + pw.SizedBox(height: 4), + pw.Text( + 'Phone: +880-4427-56072, Email: info@pstu.ac.bd', + style: pw.TextStyle( + font: font, + fontSize: 12, + color: PdfColors.blue600, + ), + textAlign: pw.TextAlign.center, + ), + ], + ), + ); + } + + static pw.Widget _buildReceiptTitle( + AcademicFeeModel fee, + pw.Font fontBold, + pw.Font font, + ) { + return pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(15), + decoration: pw.BoxDecoration( + color: PdfColors.blue100, + borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), + ), + child: pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'PAYMENT RECEIPT', + style: pw.TextStyle( + font: fontBold, + fontSize: 20, + color: PdfColors.blue900, + ), + ), + pw.SizedBox(height: 4), + pw.Text( + 'Academic Fee Payment', + style: pw.TextStyle( + font: font, + fontSize: 14, + color: PdfColors.blue700, + ), + ), + ], + ), + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + pw.Text( + 'Receipt No.', + style: pw.TextStyle( + font: font, + fontSize: 12, + color: PdfColors.blue600, + ), + ), + pw.Text( + fee.receiptNumber ?? 'N/A', + style: pw.TextStyle( + font: fontBold, + fontSize: 16, + color: PdfColors.blue900, + ), + ), + pw.SizedBox(height: 8), + pw.Text( + 'Date: ${DateFormat('MMM dd, yyyy').format(fee.paidAt ?? DateTime.now())}', + style: pw.TextStyle( + font: font, + fontSize: 12, + color: PdfColors.blue700, + ), + ), + ], + ), + ], + ), + ); + } + + static pw.Widget _buildStudentInfo( + UserModel user, + pw.Font fontBold, + pw.Font font, + ) { + return pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(15), + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.grey300), + borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), + ), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'STUDENT INFORMATION', + style: pw.TextStyle( + font: fontBold, + fontSize: 16, + color: PdfColors.blue900, + ), + ), + pw.SizedBox(height: 12), + pw.Row( + children: [ + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + _buildInfoRow('Name', user.name, fontBold, font), + _buildInfoRow('Student ID', user.id, fontBold, font), + _buildInfoRow('Registration', user.regi, fontBold, font), + ], + ), + ), + pw.SizedBox(width: 20), + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + _buildInfoRow('Faculty', user.faculty, fontBold, font), + _buildInfoRow('Email', user.email, fontBold, font), + _buildInfoRow('Phone', user.phone, fontBold, font), + ], + ), + ), + ], + ), + ], + ), + ); + } + + static pw.Widget _buildInfoRow( + String label, + String value, + pw.Font fontBold, + pw.Font font, + ) { + return pw.Padding( + padding: const pw.EdgeInsets.only(bottom: 8), + child: pw.Row( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.SizedBox( + width: 80, + child: pw.Text( + '$label:', + style: pw.TextStyle( + font: fontBold, + fontSize: 12, + color: PdfColors.grey700, + ), + ), + ), + pw.Expanded( + child: pw.Text( + value, + style: pw.TextStyle( + font: font, + fontSize: 12, + color: PdfColors.black, + ), + ), + ), + ], + ), + ); + } + + static pw.Widget _buildPaymentDetails( + AcademicFeeModel fee, + pw.Font fontBold, + pw.Font font, + ) { + return pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(15), + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.grey300), + borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), + ), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'PAYMENT DETAILS', + style: pw.TextStyle( + font: fontBold, + fontSize: 16, + color: PdfColors.blue900, + ), + ), + pw.SizedBox(height: 12), + pw.Row( + children: [ + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + _buildInfoRow('Purpose', fee.purpose, fontBold, font), + _buildInfoRow( + 'Academic Year', + fee.academicYear, + fontBold, + font, + ), + _buildInfoRow('Semester', fee.semester, fontBold, font), + ], + ), + ), + pw.SizedBox(width: 20), + pw.Expanded( + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + _buildInfoRow( + 'Due Date', + DateFormat('MMM dd, yyyy').format(fee.deadline), + fontBold, + font, + ), + _buildInfoRow( + 'Payment Date', + DateFormat('MMM dd, yyyy').format(fee.paidAt!), + fontBold, + font, + ), + _buildInfoRow('Status', 'PAID', fontBold, font), + ], + ), + ), + ], + ), + if (fee.description != null) ...[ + pw.SizedBox(height: 8), + _buildInfoRow('Description', fee.description!, fontBold, font), + ], + ], + ), + ); + } + + static pw.Widget _buildFeeItemsTable( + AcademicFeeModel fee, + pw.Font fontBold, + pw.Font font, + ) { + if (fee.items.isEmpty) { + return pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(15), + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.grey300), + borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), + ), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'FEE BREAKDOWN', + style: pw.TextStyle( + font: fontBold, + fontSize: 16, + color: PdfColors.blue900, + ), + ), + pw.SizedBox(height: 12), + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Text( + fee.purpose, + style: pw.TextStyle( + font: font, + fontSize: 14, + color: PdfColors.black, + ), + ), + pw.Text( + 'เงณ${fee.amount.toStringAsFixed(2)}', + style: pw.TextStyle( + font: fontBold, + fontSize: 14, + color: PdfColors.black, + ), + ), + ], + ), + ], + ), + ); + } + + return pw.Container( + width: double.infinity, + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.grey300), + borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), + ), + child: pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Container( + width: double.infinity, + padding: const pw.EdgeInsets.all(15), + decoration: const pw.BoxDecoration( + color: PdfColors.blue100, + borderRadius: pw.BorderRadius.only( + topLeft: pw.Radius.circular(8), + topRight: pw.Radius.circular(8), + ), + ), + child: pw.Text( + 'FEE BREAKDOWN', + style: pw.TextStyle( + font: fontBold, + fontSize: 16, + color: PdfColors.blue900, + ), + ), + ), + pw.Table( + border: pw.TableBorder.all(color: PdfColors.grey300, width: 0.5), + children: [ + // Header row + pw.TableRow( + decoration: const pw.BoxDecoration(color: PdfColors.grey100), + children: [ + _buildTableCell('Description', fontBold, isHeader: true), + _buildTableCell('Amount (เงณ)', fontBold, isHeader: true), + ], + ), + // Data rows + ...fee.items.map( + (item) => pw.TableRow( + children: [ + _buildTableCell( + '${item.name}${item.description != null ? '\n${item.description}' : ''}', + font, + ), + _buildTableCell(item.amount.toStringAsFixed(2), font), + ], + ), + ), + ], + ), + ], + ), + ); + } + + static pw.Widget _buildTableCell( + String text, + pw.Font font, { + bool isHeader = false, + }) { + return pw.Container( + padding: const pw.EdgeInsets.all(10), + child: pw.Text( + text, + style: pw.TextStyle( + font: font, + fontSize: isHeader ? 12 : 11, + color: isHeader ? PdfColors.blue900 : PdfColors.black, + ), + ), + ); + } + + static pw.Widget _buildTotalSection( + AcademicFeeModel fee, + pw.Font fontBold, + pw.Font font, + ) { + return pw.Container( + width: double.infinity, + decoration: pw.BoxDecoration( + border: pw.Border.all(color: PdfColors.blue300, width: 2), + borderRadius: const pw.BorderRadius.all(pw.Radius.circular(8)), + gradient: const pw.LinearGradient( + colors: [PdfColors.blue50, PdfColors.white], + begin: pw.Alignment.topCenter, + end: pw.Alignment.bottomCenter, + ), + ), + child: pw.Column( + children: [ + if (fee.lateFee != null && fee.lateFee! > 0) ...[ + _buildTotalRow('Subtotal', fee.amount, fontBold, font, false), + _buildTotalRow('Late Fee', fee.lateFee!, fontBold, font, false), + ], + if (fee.discount != null && fee.discount! > 0) + _buildTotalRow('Discount', -fee.discount!, fontBold, font, false), + _buildTotalRow('Total Amount', fee.totalAmount, fontBold, font, true), + ], + ), + ); + } + + static pw.Widget _buildTotalRow( + String label, + double amount, + pw.Font fontBold, + pw.Font font, + bool isTotal, + ) { + return pw.Container( + padding: const pw.EdgeInsets.symmetric(horizontal: 20, vertical: 12), + decoration: + isTotal + ? const pw.BoxDecoration( + color: PdfColors.blue900, + borderRadius: pw.BorderRadius.all(pw.Radius.circular(6)), + ) + : null, + child: pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Text( + label, + style: pw.TextStyle( + font: isTotal ? fontBold : font, + fontSize: isTotal ? 16 : 14, + color: isTotal ? PdfColors.white : PdfColors.blue900, + ), + ), + pw.Text( + 'เงณ${amount.toStringAsFixed(2)}', + style: pw.TextStyle( + font: fontBold, + fontSize: isTotal ? 18 : 14, + color: isTotal ? PdfColors.white : PdfColors.blue900, + ), + ), + ], + ), + ); + } + + static pw.Widget _buildFooter(pw.Font font) { + return pw.Column( + children: [ + pw.Divider(color: PdfColors.grey400), + pw.SizedBox(height: 15), + pw.Row( + mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, + children: [ + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + pw.Text( + 'Generated on: ${DateFormat('MMM dd, yyyy - hh:mm a').format(DateTime.now())}', + style: pw.TextStyle( + font: font, + fontSize: 10, + color: PdfColors.grey600, + ), + ), + pw.SizedBox(height: 4), + pw.Text( + 'This is a computer generated receipt.', + style: pw.TextStyle( + font: font, + fontSize: 10, + color: PdfColors.grey600, + ), + ), + ], + ), + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.end, + children: [ + pw.Text( + 'For queries, contact:', + style: pw.TextStyle( + font: font, + fontSize: 10, + color: PdfColors.grey600, + ), + ), + pw.Text( + 'accounts@pstu.ac.bd', + style: pw.TextStyle( + font: font, + fontSize: 10, + color: PdfColors.blue700, + ), + ), + ], + ), + ], + ), + ], + ); + } + + static pw.Widget _buildWatermark() { + return pw.Positioned.fill( + child: pw.Center( + child: pw.Opacity( + opacity: 0.1, // Simulate transparency + child: pw.Transform.rotate( + angle: 0.5, // ~-30 degrees + child: pw.Text( + 'PAID', + style: pw.TextStyle( + fontSize: 200, + color: PdfColors.red, + fontWeight: pw.FontWeight.bold, + letterSpacing: 5, + ), + ), + ), + ), + ), + ); + } + + static Future saveReceiptPdf({ + required AcademicFeeModel fee, + required UserModel user, + }) async { + final pdf = pw.Document(); + final font = await PdfGoogleFonts.notoSansRegular(); + final fontBold = await PdfGoogleFonts.notoSansBold(); + + pdf.addPage( + pw.Page( + pageFormat: PdfPageFormat.a4, + margin: const pw.EdgeInsets.all(40), + build: (pw.Context context) { + return pw.Stack( + children: [ + // Main content + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + _buildHeader(fontBold, font), + pw.SizedBox(height: 30), + _buildReceiptTitle(fee, fontBold, font), + pw.SizedBox(height: 25), + _buildStudentInfo(user, fontBold, font), + pw.SizedBox(height: 25), + _buildPaymentDetails(fee, fontBold, font), + pw.SizedBox(height: 25), + _buildFeeItemsTable(fee, fontBold, font), + pw.SizedBox(height: 25), + _buildTotalSection(fee, fontBold, font), + pw.SizedBox(height: 30), + _buildFooter(font), + ], + ), + // Watermark + _buildWatermark(), + ], + ); + }, + ), + ); + + final directory = await getApplicationDocumentsDirectory(); + final file = File('${directory.path}/receipt_${fee.receiptNumber}.pdf'); + await file.writeAsBytes(await pdf.save()); + + return file; + } + + // New method to generate and email receipt + static Future generateAndEmailReceipt({ + required AcademicFeeModel fee, + required UserModel user, + }) async { + final pdf = pw.Document(); + + // Load custom font if available + final font = await PdfGoogleFonts.notoSansRegular(); + final fontBold = await PdfGoogleFonts.notoSansBold(); + + pdf.addPage( + pw.Page( + pageFormat: PdfPageFormat.a4, + margin: const pw.EdgeInsets.all(40), + build: (pw.Context context) { + return pw.Stack( + children: [ + // Main content + pw.Column( + crossAxisAlignment: pw.CrossAxisAlignment.start, + children: [ + // Header with University Name + _buildHeader(fontBold, font), + pw.SizedBox(height: 30), + + // Receipt Title and Number + _buildReceiptTitle(fee, fontBold, font), + pw.SizedBox(height: 25), + + // Student Information + _buildStudentInfo(user, fontBold, font), + pw.SizedBox(height: 25), + + // Payment Details + _buildPaymentDetails(fee, fontBold, font), + pw.SizedBox(height: 25), + + // Fee Items Table + _buildFeeItemsTable(fee, fontBold, font), + pw.SizedBox(height: 25), + + // Total and Payment Info + _buildTotalSection(fee, fontBold, font), + pw.SizedBox(height: 30), + + // Footer + _buildFooter(font), + ], + ), + // Watermark + _buildWatermark(), + ], + ); + }, + ), + ); + + // Save PDF to file + final directory = await getApplicationDocumentsDirectory(); + final file = File('${directory.path}/receipt_${fee.receiptNumber}.pdf'); + await file.writeAsBytes(await pdf.save()); + + // Send email with receipt + try { + await _emailService.sendPaymentConfirmationEmail( + receiverEmail: user.email, + studentName: user.name, + receiptNumber: fee.receiptNumber ?? 'N/A', + amount: fee.totalAmount, + purpose: fee.purpose, + academicYear: fee.academicYear, + semester: fee.semester, + paymentDate: fee.paidAt ?? DateTime.now(), + receiptPdf: file, + paymentData: { + 'receipt_number': fee.receiptNumber, + 'amount': fee.totalAmount, + 'purpose': fee.purpose, + }, + ); + + // Format SMS message + final message = Uri.encodeComponent( + 'Payment Confirmation: BDT ${fee.totalAmount.toStringAsFixed(2)} ' + 'has been received for ${fee.purpose}. ' + 'Receipt ID #${fee.receiptNumber ?? 'N/A'}. Thank you.\n' + '-MyEdu (PSTU)', + ); + + // Send SMS notification + final smsUrl = + 'http://bulksmsbd.net/api/smsapi?api_key=YOUR_API_KEY' + '&type=text&number=${user.phone}&senderid=SENDER_ID&message=$message'; + + await http.get(Uri.parse(smsUrl)); + print('SMS sent successfully to ${user.phone}'); + } catch (e) { + print('Failed to send payment confirmation email: $e'); + rethrow; + } + } +} diff --git a/lib/services/push_notification_service.dart b/lib/services/push_notification_service.dart index f8e29ef..dac81d5 100644 --- a/lib/services/push_notification_service.dart +++ b/lib/services/push_notification_service.dart @@ -1,428 +1,428 @@ -import 'dart:convert'; -import 'dart:io'; -import 'package:awesome_notifications/awesome_notifications.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; - -class PushNotificationService { - static final FirebaseMessaging _firebaseMessaging = - FirebaseMessaging.instance; - static String? _fcmToken; - - // Notification channel details - static const String channelKey = 'basic_channel'; - static const String channelName = 'Basic Notifications'; - static const String channelDescription = - 'Notification channel for basic app notifications'; - static const String channelGroupKey = 'basic_channel_group'; - static const String channelGroupName = 'Basic Group'; - - /// Initialize the push notification service - static Future initialize() async { - try { - // Initialize Awesome Notifications - await _initializeAwesomeNotifications(); - - // Initialize Firebase Messaging - await _initializeFirebaseMessaging(); - - // Request permissions - await _requestPermissions(); - - // Subscribe to topic 'pn' - await _subscribeToTopic(); - - // Setup message handlers - _setupMessageHandlers(); - - // Get FCM token - await _getFCMToken(); - - if (kDebugMode) { - print('โœ… Push Notification Service initialized successfully'); - } - } catch (e) { - if (kDebugMode) { - print('โŒ Error initializing Push Notification Service: $e'); - } - } - } - - /// Initialize Awesome Notifications - static Future _initializeAwesomeNotifications() async { - await AwesomeNotifications().initialize( - 'resource://drawable/ic_notification', // App icon for notifications - [ - NotificationChannel( - channelKey: channelKey, - channelName: channelName, - channelDescription: channelDescription, - channelGroupKey: channelGroupKey, - defaultColor: const Color(0xFF6366F1), - ledColor: const Color(0xFF6366F1), - importance: NotificationImportance.High, - channelShowBadge: true, - onlyAlertOnce: true, - playSound: true, - criticalAlerts: true, - ), - ], - channelGroups: [ - NotificationChannelGroup( - channelGroupKey: channelGroupKey, - channelGroupName: channelGroupName, - ), - ], - debug: kDebugMode, - ); - } - - /// Initialize Firebase Messaging - static Future _initializeFirebaseMessaging() async { - // Configure Firebase Messaging settings - await _firebaseMessaging.setForegroundNotificationPresentationOptions( - alert: true, - badge: true, - sound: true, - ); - } - - /// Request notification permissions - static Future _requestPermissions() async { - // Request Firebase Messaging permissions - NotificationSettings settings = await _firebaseMessaging.requestPermission( - alert: true, - announcement: false, - badge: true, - carPlay: false, - criticalAlert: false, - provisional: false, - sound: true, - ); - - // Request Awesome Notifications permissions - bool isAllowed = await AwesomeNotifications().isNotificationAllowed(); - if (!isAllowed) { - await AwesomeNotifications().requestPermissionToSendNotifications(); - } - - if (kDebugMode) { - print( - 'Firebase Messaging permission status: ${settings.authorizationStatus}', - ); - print('Awesome Notifications permission: $isAllowed'); - } - } - - /// Subscribe to the 'pn' topic - static Future _subscribeToTopic() async { - try { - await _firebaseMessaging.subscribeToTopic('pn'); - if (kDebugMode) { - print('โœ… Successfully subscribed to topic: pn'); - } - } catch (e) { - if (kDebugMode) { - print('โŒ Error subscribing to topic: $e'); - } - } - } - - /// Setup message handlers for different app states - static void _setupMessageHandlers() { - // Handle messages when app is in foreground - FirebaseMessaging.onMessage.listen(_handleForegroundMessage); - - // Handle notification taps when app is in background or terminated - FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage); - - // Handle notification taps when app is terminated - _handleTerminatedAppMessage(); - } - - /// Handle messages when app is in foreground - static Future _handleForegroundMessage(RemoteMessage message) async { - if (kDebugMode) { - print('๐Ÿ“ฑ Received foreground message: ${message.messageId}'); - print('Title: ${message.notification?.title}'); - print('Body: ${message.notification?.body}'); - print('Data: ${message.data}'); - } - - await _showLocalNotification(message); - } - - /// Handle notification taps when app is in background - static Future _handleBackgroundMessage(RemoteMessage message) async { - if (kDebugMode) { - print('๐Ÿ“ฑ Opened app from background notification: ${message.messageId}'); - } - - _handleNotificationTap(message); - } - - /// Handle notification taps when app was terminated - static Future _handleTerminatedAppMessage() async { - RemoteMessage? initialMessage = - await _firebaseMessaging.getInitialMessage(); - - if (initialMessage != null) { - if (kDebugMode) { - print( - '๐Ÿ“ฑ Opened app from terminated state: ${initialMessage.messageId}', - ); - } - - _handleNotificationTap(initialMessage); - } - } - - /// Show local notification using Awesome Notifications - static Future _showLocalNotification(RemoteMessage message) async { - try { - String title = message.notification?.title ?? 'MyEdu Notification'; - String body = message.notification?.body ?? 'You have a new notification'; - - // Extract custom data - Map customData = {}; - message.data.forEach((key, value) { - customData[key] = value.toString(); - }); - - await AwesomeNotifications().createNotification( - content: NotificationContent( - id: DateTime.now().millisecondsSinceEpoch.remainder(100000), - channelKey: channelKey, - title: title, - body: body, - icon: 'resource://drawable/ic_notification', // Add small icon here - bigPicture: message.notification?.android?.imageUrl, - largeIcon: message.notification?.android?.imageUrl, - notificationLayout: - message.notification?.android?.imageUrl != null - ? NotificationLayout.BigPicture - : NotificationLayout.Default, - category: NotificationCategory.Message, - wakeUpScreen: true, - fullScreenIntent: false, - criticalAlert: true, - color: const Color(0xFF6366F1), - backgroundColor: const Color(0xFF6366F1), - payload: customData, - customSound: 'resource://raw/notification_sound', - ), - actionButtons: [ - NotificationActionButton( - key: 'OPEN', - label: 'Open', - actionType: ActionType.Default, - color: const Color(0xFF6366F1), - ), - NotificationActionButton( - key: 'DISMISS', - label: 'Dismiss', - actionType: ActionType.DismissAction, - isDangerousOption: true, - ), - ], - ); - } catch (e) { - if (kDebugMode) { - print('โŒ Error showing local notification: $e'); - } - } - } - - /// Handle notification tap actions - static void _handleNotificationTap(RemoteMessage message) { - // You can navigate to specific screens based on notification data - if (message.data.isNotEmpty) { - String? screen = message.data['screen']; - String? route = message.data['route']; - - if (screen != null || route != null) { - // Navigate to specific screen based on data - if (route != null) { - Get.toNamed(route); - } else { - _navigateBasedOnScreen(screen!); - } - } - } - } - - /// Navigate to specific screen based on notification data - static void _navigateBasedOnScreen(String screen) { - switch (screen.toLowerCase()) { - case 'notices': - Get.toNamed('/notices'); - break; - case 'dashboard': - Get.toNamed('/dashboard'); - break; - case 'fees': - // Navigate to academic fees if you have that route - break; - default: - // Default to dashboard - Get.toNamed('/dashboard'); - break; - } - } - - /// Get FCM token - static Future _getFCMToken() async { - try { - _fcmToken = await _firebaseMessaging.getToken(); - if (kDebugMode && _fcmToken != null) { - print('๐Ÿ“ฑ FCM Token: $_fcmToken'); - } - } catch (e) { - if (kDebugMode) { - print('โŒ Error getting FCM token: $e'); - } - } - } - - /// Get current FCM token - static String? get fcmToken => _fcmToken; - - /// Refresh FCM token - static Future refreshToken() async { - try { - await _firebaseMessaging.deleteToken(); - _fcmToken = await _firebaseMessaging.getToken(); - return _fcmToken; - } catch (e) { - if (kDebugMode) { - print('โŒ Error refreshing FCM token: $e'); - } - return null; - } - } - - /// Unsubscribe from topic - static Future unsubscribeFromTopic() async { - try { - await _firebaseMessaging.unsubscribeFromTopic('pn'); - if (kDebugMode) { - print('โœ… Successfully unsubscribed from topic: pn'); - } - } catch (e) { - if (kDebugMode) { - print('โŒ Error unsubscribing from topic: $e'); - } - } - } - - /// Setup notification action listeners - static void setupNotificationActionListeners() { - // Listen for notification actions - AwesomeNotifications().setListeners( - onActionReceivedMethod: _onActionReceivedMethod, - onNotificationCreatedMethod: _onNotificationCreatedMethod, - onNotificationDisplayedMethod: _onNotificationDisplayedMethod, - onDismissActionReceivedMethod: _onDismissActionReceivedMethod, - ); - } - - /// Handle notification actions - static Future _onActionReceivedMethod( - ReceivedAction receivedAction, - ) async { - if (kDebugMode) { - print( - '๐Ÿ”˜ Notification action received: ${receivedAction.buttonKeyPressed}', - ); - } - - // Handle different action buttons - switch (receivedAction.buttonKeyPressed) { - case 'OPEN': - // Navigate based on payload data - if (receivedAction.payload != null) { - String? screen = receivedAction.payload!['screen']; - String? route = receivedAction.payload!['route']; - - if (route != null) { - Get.toNamed(route); - } else if (screen != null) { - _navigateBasedOnScreen(screen); - } else { - Get.toNamed('/dashboard'); - } - } else { - Get.toNamed('/dashboard'); - } - break; - case 'DISMISS': - // Do nothing, notification is automatically dismissed - break; - } - } - - /// Handle notification creation - static Future _onNotificationCreatedMethod( - ReceivedNotification receivedNotification, - ) async { - if (kDebugMode) { - print('๐Ÿ“ Notification created: ${receivedNotification.id}'); - } - } - - /// Handle notification display - static Future _onNotificationDisplayedMethod( - ReceivedNotification receivedNotification, - ) async { - if (kDebugMode) { - print('๐Ÿ‘€ Notification displayed: ${receivedNotification.id}'); - } - } - - /// Handle notification dismiss - static Future _onDismissActionReceivedMethod( - ReceivedAction receivedAction, - ) async { - if (kDebugMode) { - print('๐Ÿ—‘๏ธ Notification dismissed: ${receivedAction.id}'); - } - } - - /// Test notification (for debugging) - static Future sendTestNotification() async { - if (kDebugMode) { - await AwesomeNotifications().createNotification( - content: NotificationContent( - id: DateTime.now().millisecondsSinceEpoch.remainder(100000), - channelKey: channelKey, - title: 'Test Notification', - body: 'This is a test notification from MyEdu', - icon: 'resource://drawable/ic_notification', // Add small icon here - category: NotificationCategory.Message, - wakeUpScreen: true, - color: const Color(0xFF6366F1), - ), - ); - } - } - - /// Dispose resources - static void dispose() { - // Clean up resources if needed - } -} - -/// Background message handler (must be top-level function) -@pragma('vm:entry-point') -Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { - if (kDebugMode) { - print('๐Ÿ“ฑ Background message received: ${message.messageId}'); - print('Title: ${message.notification?.title}'); - print('Body: ${message.notification?.body}'); - } - - // You can process the background message here if needed - // Note: You cannot show UI from background handler -} +import 'dart:convert'; +import 'dart:io'; +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class PushNotificationService { + static final FirebaseMessaging _firebaseMessaging = + FirebaseMessaging.instance; + static String? _fcmToken; + + // Notification channel details + static const String channelKey = 'basic_channel'; + static const String channelName = 'Basic Notifications'; + static const String channelDescription = + 'Notification channel for basic app notifications'; + static const String channelGroupKey = 'basic_channel_group'; + static const String channelGroupName = 'Basic Group'; + + /// Initialize the push notification service + static Future initialize() async { + try { + // Initialize Awesome Notifications + await _initializeAwesomeNotifications(); + + // Initialize Firebase Messaging + await _initializeFirebaseMessaging(); + + // Request permissions + await _requestPermissions(); + + // Subscribe to topic 'pn' + await _subscribeToTopic(); + + // Setup message handlers + _setupMessageHandlers(); + + // Get FCM token + await _getFCMToken(); + + if (kDebugMode) { + print('โœ… Push Notification Service initialized successfully'); + } + } catch (e) { + if (kDebugMode) { + print('โŒ Error initializing Push Notification Service: $e'); + } + } + } + + /// Initialize Awesome Notifications + static Future _initializeAwesomeNotifications() async { + await AwesomeNotifications().initialize( + 'resource://drawable/ic_notification', // App icon for notifications + [ + NotificationChannel( + channelKey: channelKey, + channelName: channelName, + channelDescription: channelDescription, + channelGroupKey: channelGroupKey, + defaultColor: const Color(0xFF6366F1), + ledColor: const Color(0xFF6366F1), + importance: NotificationImportance.High, + channelShowBadge: true, + onlyAlertOnce: true, + playSound: true, + criticalAlerts: true, + ), + ], + channelGroups: [ + NotificationChannelGroup( + channelGroupKey: channelGroupKey, + channelGroupName: channelGroupName, + ), + ], + debug: kDebugMode, + ); + } + + /// Initialize Firebase Messaging + static Future _initializeFirebaseMessaging() async { + // Configure Firebase Messaging settings + await _firebaseMessaging.setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + } + + /// Request notification permissions + static Future _requestPermissions() async { + // Request Firebase Messaging permissions + NotificationSettings settings = await _firebaseMessaging.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true, + ); + + // Request Awesome Notifications permissions + bool isAllowed = await AwesomeNotifications().isNotificationAllowed(); + if (!isAllowed) { + await AwesomeNotifications().requestPermissionToSendNotifications(); + } + + if (kDebugMode) { + print( + 'Firebase Messaging permission status: ${settings.authorizationStatus}', + ); + print('Awesome Notifications permission: $isAllowed'); + } + } + + /// Subscribe to the 'pn' topic + static Future _subscribeToTopic() async { + try { + await _firebaseMessaging.subscribeToTopic('pn'); + if (kDebugMode) { + print('โœ… Successfully subscribed to topic: pn'); + } + } catch (e) { + if (kDebugMode) { + print('โŒ Error subscribing to topic: $e'); + } + } + } + + /// Setup message handlers for different app states + static void _setupMessageHandlers() { + // Handle messages when app is in foreground + FirebaseMessaging.onMessage.listen(_handleForegroundMessage); + + // Handle notification taps when app is in background or terminated + FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage); + + // Handle notification taps when app is terminated + _handleTerminatedAppMessage(); + } + + /// Handle messages when app is in foreground + static Future _handleForegroundMessage(RemoteMessage message) async { + if (kDebugMode) { + print('๐Ÿ“ฑ Received foreground message: ${message.messageId}'); + print('Title: ${message.notification?.title}'); + print('Body: ${message.notification?.body}'); + print('Data: ${message.data}'); + } + + await _showLocalNotification(message); + } + + /// Handle notification taps when app is in background + static Future _handleBackgroundMessage(RemoteMessage message) async { + if (kDebugMode) { + print('๐Ÿ“ฑ Opened app from background notification: ${message.messageId}'); + } + + _handleNotificationTap(message); + } + + /// Handle notification taps when app was terminated + static Future _handleTerminatedAppMessage() async { + RemoteMessage? initialMessage = + await _firebaseMessaging.getInitialMessage(); + + if (initialMessage != null) { + if (kDebugMode) { + print( + '๐Ÿ“ฑ Opened app from terminated state: ${initialMessage.messageId}', + ); + } + + _handleNotificationTap(initialMessage); + } + } + + /// Show local notification using Awesome Notifications + static Future _showLocalNotification(RemoteMessage message) async { + try { + String title = message.notification?.title ?? 'MyEdu Notification'; + String body = message.notification?.body ?? 'You have a new notification'; + + // Extract custom data + Map customData = {}; + message.data.forEach((key, value) { + customData[key] = value.toString(); + }); + + await AwesomeNotifications().createNotification( + content: NotificationContent( + id: DateTime.now().millisecondsSinceEpoch.remainder(100000), + channelKey: channelKey, + title: title, + body: body, + icon: 'resource://drawable/ic_notification', // Add small icon here + bigPicture: message.notification?.android?.imageUrl, + largeIcon: message.notification?.android?.imageUrl, + notificationLayout: + message.notification?.android?.imageUrl != null + ? NotificationLayout.BigPicture + : NotificationLayout.Default, + category: NotificationCategory.Message, + wakeUpScreen: true, + fullScreenIntent: false, + criticalAlert: true, + color: const Color(0xFF6366F1), + backgroundColor: const Color(0xFF6366F1), + payload: customData, + customSound: 'resource://raw/notification_sound', + ), + actionButtons: [ + NotificationActionButton( + key: 'OPEN', + label: 'Open', + actionType: ActionType.Default, + color: const Color(0xFF6366F1), + ), + NotificationActionButton( + key: 'DISMISS', + label: 'Dismiss', + actionType: ActionType.DismissAction, + isDangerousOption: true, + ), + ], + ); + } catch (e) { + if (kDebugMode) { + print('โŒ Error showing local notification: $e'); + } + } + } + + /// Handle notification tap actions + static void _handleNotificationTap(RemoteMessage message) { + // You can navigate to specific screens based on notification data + if (message.data.isNotEmpty) { + String? screen = message.data['screen']; + String? route = message.data['route']; + + if (screen != null || route != null) { + // Navigate to specific screen based on data + if (route != null) { + Get.toNamed(route); + } else { + _navigateBasedOnScreen(screen!); + } + } + } + } + + /// Navigate to specific screen based on notification data + static void _navigateBasedOnScreen(String screen) { + switch (screen.toLowerCase()) { + case 'notices': + Get.toNamed('/notices'); + break; + case 'dashboard': + Get.toNamed('/dashboard'); + break; + case 'fees': + // Navigate to academic fees if you have that route + break; + default: + // Default to dashboard + Get.toNamed('/dashboard'); + break; + } + } + + /// Get FCM token + static Future _getFCMToken() async { + try { + _fcmToken = await _firebaseMessaging.getToken(); + if (kDebugMode && _fcmToken != null) { + print('๐Ÿ“ฑ FCM Token: $_fcmToken'); + } + } catch (e) { + if (kDebugMode) { + print('โŒ Error getting FCM token: $e'); + } + } + } + + /// Get current FCM token + static String? get fcmToken => _fcmToken; + + /// Refresh FCM token + static Future refreshToken() async { + try { + await _firebaseMessaging.deleteToken(); + _fcmToken = await _firebaseMessaging.getToken(); + return _fcmToken; + } catch (e) { + if (kDebugMode) { + print('โŒ Error refreshing FCM token: $e'); + } + return null; + } + } + + /// Unsubscribe from topic + static Future unsubscribeFromTopic() async { + try { + await _firebaseMessaging.unsubscribeFromTopic('pn'); + if (kDebugMode) { + print('โœ… Successfully unsubscribed from topic: pn'); + } + } catch (e) { + if (kDebugMode) { + print('โŒ Error unsubscribing from topic: $e'); + } + } + } + + /// Setup notification action listeners + static void setupNotificationActionListeners() { + // Listen for notification actions + AwesomeNotifications().setListeners( + onActionReceivedMethod: _onActionReceivedMethod, + onNotificationCreatedMethod: _onNotificationCreatedMethod, + onNotificationDisplayedMethod: _onNotificationDisplayedMethod, + onDismissActionReceivedMethod: _onDismissActionReceivedMethod, + ); + } + + /// Handle notification actions + static Future _onActionReceivedMethod( + ReceivedAction receivedAction, + ) async { + if (kDebugMode) { + print( + '๐Ÿ”˜ Notification action received: ${receivedAction.buttonKeyPressed}', + ); + } + + // Handle different action buttons + switch (receivedAction.buttonKeyPressed) { + case 'OPEN': + // Navigate based on payload data + if (receivedAction.payload != null) { + String? screen = receivedAction.payload!['screen']; + String? route = receivedAction.payload!['route']; + + if (route != null) { + Get.toNamed(route); + } else if (screen != null) { + _navigateBasedOnScreen(screen); + } else { + Get.toNamed('/dashboard'); + } + } else { + Get.toNamed('/dashboard'); + } + break; + case 'DISMISS': + // Do nothing, notification is automatically dismissed + break; + } + } + + /// Handle notification creation + static Future _onNotificationCreatedMethod( + ReceivedNotification receivedNotification, + ) async { + if (kDebugMode) { + print('๐Ÿ“ Notification created: ${receivedNotification.id}'); + } + } + + /// Handle notification display + static Future _onNotificationDisplayedMethod( + ReceivedNotification receivedNotification, + ) async { + if (kDebugMode) { + print('๐Ÿ‘€ Notification displayed: ${receivedNotification.id}'); + } + } + + /// Handle notification dismiss + static Future _onDismissActionReceivedMethod( + ReceivedAction receivedAction, + ) async { + if (kDebugMode) { + print('๐Ÿ—‘๏ธ Notification dismissed: ${receivedAction.id}'); + } + } + + /// Test notification (for debugging) + static Future sendTestNotification() async { + if (kDebugMode) { + await AwesomeNotifications().createNotification( + content: NotificationContent( + id: DateTime.now().millisecondsSinceEpoch.remainder(100000), + channelKey: channelKey, + title: 'Test Notification', + body: 'This is a test notification from MyEdu', + icon: 'resource://drawable/ic_notification', // Add small icon here + category: NotificationCategory.Message, + wakeUpScreen: true, + color: const Color(0xFF6366F1), + ), + ); + } + } + + /// Dispose resources + static void dispose() { + // Clean up resources if needed + } +} + +/// Background message handler (must be top-level function) +@pragma('vm:entry-point') +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { + if (kDebugMode) { + print('๐Ÿ“ฑ Background message received: ${message.messageId}'); + print('Title: ${message.notification?.title}'); + print('Body: ${message.notification?.body}'); + } + + // You can process the background message here if needed + // Note: You cannot show UI from background handler +} diff --git a/lib/utils/theme.dart b/lib/utils/theme.dart index cea76d2..a089624 100644 --- a/lib/utils/theme.dart +++ b/lib/utils/theme.dart @@ -1,239 +1,239 @@ -import 'package:flutter/material.dart'; - -class AppTheme { - static const Color primaryColor = Color(0xFF2E7D32); - static const Color secondaryColor = Color(0xFF1976D2); - static const Color accentColor = Color(0xFF00ACC1); - static const Color errorColor = Color(0xFFE53935); - static const Color successColor = Color(0xFF43A047); - - // Light Theme - static ThemeData lightTheme = ThemeData( - useMaterial3: true, - brightness: Brightness.light, - primarySwatch: Colors.green, - primaryColor: primaryColor, - scaffoldBackgroundColor: Colors.grey[50], - - colorScheme: const ColorScheme.light( - primary: primaryColor, - secondary: secondaryColor, - tertiary: accentColor, - error: errorColor, - surface: Colors.white, - onPrimary: Colors.white, - onSecondary: Colors.white, - onSurface: Colors.black87, - onError: Colors.white, - ), - - appBarTheme: const AppBarTheme( - backgroundColor: primaryColor, - foregroundColor: Colors.white, - elevation: 0, - centerTitle: true, - titleTextStyle: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - backgroundColor: primaryColor, - foregroundColor: Colors.white, - elevation: 2, - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ), - - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - foregroundColor: primaryColor, - side: const BorderSide(color: primaryColor, width: 2), - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ), - - inputDecorationTheme: InputDecorationTheme( - filled: true, - fillColor: Colors.grey[100], - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: Colors.grey[300]!, width: 1), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: primaryColor, width: 2), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: errorColor, width: 2), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: errorColor, width: 2), - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - labelStyle: const TextStyle(color: Colors.grey), - hintStyle: TextStyle(color: Colors.grey[500]), - ), - - cardTheme: CardTheme( - color: Colors.white, - elevation: 4, - shadowColor: Colors.black26, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - margin: const EdgeInsets.all(8), - ), - - textTheme: const TextTheme( - headlineLarge: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - headlineMedium: TextStyle( - fontSize: 28, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - titleLarge: TextStyle( - fontSize: 22, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - titleMedium: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Colors.black87, - ), - bodyLarge: TextStyle(fontSize: 16, color: Colors.black87), - bodyMedium: TextStyle(fontSize: 14, color: Colors.black87), - ), - ); - - // Dark Theme - static ThemeData darkTheme = ThemeData( - useMaterial3: true, - brightness: Brightness.dark, - primarySwatch: Colors.green, - primaryColor: primaryColor, - scaffoldBackgroundColor: const Color(0xFF121212), - - colorScheme: const ColorScheme.dark( - primary: primaryColor, - secondary: secondaryColor, - tertiary: accentColor, - error: errorColor, - surface: Color(0xFF1E1E1E), - onPrimary: Colors.white, - onSecondary: Colors.white, - onSurface: Colors.white, - onError: Colors.white, - ), - - appBarTheme: const AppBarTheme( - backgroundColor: Color(0xFF1E1E1E), - foregroundColor: Colors.white, - elevation: 0, - centerTitle: true, - titleTextStyle: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - backgroundColor: primaryColor, - foregroundColor: Colors.white, - elevation: 2, - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ), - - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - foregroundColor: primaryColor, - side: const BorderSide(color: primaryColor, width: 2), - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - ), - - inputDecorationTheme: InputDecorationTheme( - filled: true, - fillColor: const Color(0xFF2C2C2C), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: Color(0xFF404040), width: 1), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: primaryColor, width: 2), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: errorColor, width: 2), - ), - focusedErrorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: errorColor, width: 2), - ), - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - labelStyle: const TextStyle(color: Colors.grey), - hintStyle: const TextStyle(color: Color(0xFF8A8A8A)), - ), - - cardTheme: CardTheme( - color: const Color(0xFF1E1E1E), - elevation: 4, - shadowColor: Colors.black54, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - margin: const EdgeInsets.all(8), - ), - - textTheme: const TextTheme( - headlineLarge: TextStyle( - fontSize: 32, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - headlineMedium: TextStyle( - fontSize: 28, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - titleLarge: TextStyle( - fontSize: 22, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - titleMedium: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - bodyLarge: TextStyle(fontSize: 16, color: Colors.white), - bodyMedium: TextStyle(fontSize: 14, color: Colors.white), - ), - ); -} +import 'package:flutter/material.dart'; + +class AppTheme { + static const Color primaryColor = Color(0xFF2E7D32); + static const Color secondaryColor = Color(0xFF1976D2); + static const Color accentColor = Color(0xFF00ACC1); + static const Color errorColor = Color(0xFFE53935); + static const Color successColor = Color(0xFF43A047); + + // Light Theme + static ThemeData lightTheme = ThemeData( + useMaterial3: true, + brightness: Brightness.light, + primarySwatch: Colors.green, + primaryColor: primaryColor, + scaffoldBackgroundColor: Colors.grey[50], + + colorScheme: const ColorScheme.light( + primary: primaryColor, + secondary: secondaryColor, + tertiary: accentColor, + error: errorColor, + surface: Colors.white, + onPrimary: Colors.white, + onSecondary: Colors.white, + onSurface: Colors.black87, + onError: Colors.white, + ), + + appBarTheme: const AppBarTheme( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + elevation: 0, + centerTitle: true, + titleTextStyle: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + elevation: 2, + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: primaryColor, + side: const BorderSide(color: primaryColor, width: 2), + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: Colors.grey[100], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Colors.grey[300]!, width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: primaryColor, width: 2), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: errorColor, width: 2), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: errorColor, width: 2), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + labelStyle: const TextStyle(color: Colors.grey), + hintStyle: TextStyle(color: Colors.grey[500]), + ), + + cardTheme: CardThemeData( + color: Colors.white, + elevation: 4, + shadowColor: Colors.black26, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + margin: const EdgeInsets.all(8), + ), + + textTheme: const TextTheme( + headlineLarge: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + headlineMedium: TextStyle( + fontSize: 28, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + titleLarge: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + titleMedium: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + bodyLarge: TextStyle(fontSize: 16, color: Colors.black87), + bodyMedium: TextStyle(fontSize: 14, color: Colors.black87), + ), + ); + + // Dark Theme + static ThemeData darkTheme = ThemeData( + useMaterial3: true, + brightness: Brightness.dark, + primarySwatch: Colors.green, + primaryColor: primaryColor, + scaffoldBackgroundColor: const Color(0xFF121212), + + colorScheme: const ColorScheme.dark( + primary: primaryColor, + secondary: secondaryColor, + tertiary: accentColor, + error: errorColor, + surface: Color(0xFF1E1E1E), + onPrimary: Colors.white, + onSecondary: Colors.white, + onSurface: Colors.white, + onError: Colors.white, + ), + + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFF1E1E1E), + foregroundColor: Colors.white, + elevation: 0, + centerTitle: true, + titleTextStyle: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + elevation: 2, + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: primaryColor, + side: const BorderSide(color: primaryColor, width: 2), + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + ), + ), + + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: const Color(0xFF2C2C2C), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: Color(0xFF404040), width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: primaryColor, width: 2), + ), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: errorColor, width: 2), + ), + focusedErrorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: errorColor, width: 2), + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + labelStyle: const TextStyle(color: Colors.grey), + hintStyle: const TextStyle(color: Color(0xFF8A8A8A)), + ), + + cardTheme: CardThemeData( + color: const Color(0xFF1E1E1E), + elevation: 4, + shadowColor: Colors.black54, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + margin: const EdgeInsets.all(8), + ), + + textTheme: const TextTheme( + headlineLarge: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + headlineMedium: TextStyle( + fontSize: 28, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + titleLarge: TextStyle( + fontSize: 22, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + titleMedium: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + bodyLarge: TextStyle(fontSize: 16, color: Colors.white), + bodyMedium: TextStyle(fontSize: 14, color: Colors.white), + ), + ); +} diff --git a/lib/views/academic_details_screen.dart b/lib/views/academic_details_screen.dart index e410c0f..6b80579 100644 --- a/lib/views/academic_details_screen.dart +++ b/lib/views/academic_details_screen.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import '../controllers/academic_result_controller.dart'; import '../controllers/auth_controller.dart'; +import 'academic_result_screen.dart'; class AcademicDetailsScreen extends StatefulWidget { - const AcademicDetailsScreen({Key? key}) : super(key: key); + const AcademicDetailsScreen({super.key}); @override State createState() => _AcademicDetailsScreenState(); @@ -12,6 +14,11 @@ class AcademicDetailsScreen extends StatefulWidget { class _AcademicDetailsScreenState extends State with TickerProviderStateMixin { + final AcademicResultController resultController = + Get.isRegistered() + ? Get.find() + : Get.put(AcademicResultController()); + late AnimationController _animationController; late AnimationController _pulseController; late Animation _fadeAnimation; @@ -89,9 +96,11 @@ class _AcademicDetailsScreenState extends State margin: const EdgeInsets.all(16), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), + color: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(25), - border: Border.all(color: Colors.white.withOpacity(0.2)), + border: Border.all( + color: Colors.white.withValues(alpha: 0.2), + ), ), child: Row( children: [ @@ -100,7 +109,7 @@ class _AcademicDetailsScreenState extends State icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon( @@ -127,7 +136,7 @@ class _AcademicDetailsScreenState extends State icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon( @@ -152,14 +161,14 @@ class _AcademicDetailsScreenState extends State margin: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.all(24), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), + color: Colors.white.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(25), border: Border.all( - color: Colors.white.withOpacity(0.3), + color: Colors.white.withValues(alpha: 0.3), ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 10), ), @@ -190,12 +199,16 @@ class _AcademicDetailsScreenState extends State ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues( + alpha: 0.2, + ), blurRadius: 25, offset: const Offset(0, 12), ), BoxShadow( - color: Colors.white.withOpacity(0.8), + color: Colors.white.withValues( + alpha: 0.8, + ), blurRadius: 15, offset: const Offset(-5, -5), ), @@ -240,7 +253,7 @@ class _AcademicDetailsScreenState extends State vertical: 6, ), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(20), ), child: Text( @@ -269,7 +282,7 @@ class _AcademicDetailsScreenState extends State Text( 'Active Student', style: TextStyle( - color: Colors.white.withOpacity(0.9), + color: Colors.white.withValues(alpha: 0.9), fontSize: 14, fontWeight: FontWeight.w500, ), @@ -370,6 +383,7 @@ class _AcademicDetailsScreenState extends State 4, Colors.red, ), + Obx(() => _buildAcademicSummaryCard()), const SizedBox(height: 20), ], ), @@ -443,7 +457,7 @@ class _AcademicDetailsScreenState extends State borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.08), + color: Colors.grey.withValues(alpha: 0.08), blurRadius: 15, offset: const Offset(0, 8), ), @@ -473,10 +487,10 @@ class _AcademicDetailsScreenState extends State width: 56, height: 56, decoration: BoxDecoration( - color: iconColor.withOpacity(0.1), + color: iconColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), border: Border.all( - color: iconColor.withOpacity(0.2), + color: iconColor.withValues(alpha: 0.2), width: 1, ), ), @@ -524,4 +538,130 @@ class _AcademicDetailsScreenState extends State }, ); } + + Widget _buildAcademicSummaryCard() { + if (resultController.isLoading.value) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.08), + blurRadius: 15, + offset: const Offset(0, 8), + ), + ], + ), + child: const Center(child: CircularProgressIndicator()), + ); + } + + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.08), + blurRadius: 15, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: const Color(0xFF667eea).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(16), + ), + child: const Icon( + Icons.bar_chart, + color: Color(0xFF667eea), + size: 26, + ), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Academic Results', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: Color(0xFF2c3e50), + ), + ), + const SizedBox(height: 4), + Text( + resultController.isUsingSampleResults.value + ? 'Sample semester results are shown' + : 'Official semester results', + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + ), + ), + TextButton( + onPressed: () => Get.to(() => const AcademicResultScreen()), + child: const Text('View'), + ), + ], + ), + const SizedBox(height: 18), + Row( + children: [ + _buildSummaryMetric( + 'GPA', + resultController.overallGPA.value.toStringAsFixed(2), + ), + _buildSummaryMetric( + 'Credits', + resultController.totalCredits.value.toStringAsFixed(1), + ), + _buildSummaryMetric( + 'Semesters', + resultController.academicResults.length.toString(), + ), + ], + ), + ], + ), + ); + } + + Widget _buildSummaryMetric(String label, String value) { + return Expanded( + child: Column( + children: [ + Text( + value, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF667eea), + ), + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + ], + ), + ); + } } diff --git a/lib/views/academic_fees_screen.dart b/lib/views/academic_fees_screen.dart index d29dc70..641eab0 100644 --- a/lib/views/academic_fees_screen.dart +++ b/lib/views/academic_fees_screen.dart @@ -1,1365 +1,1365 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:shimmer/shimmer.dart'; -import 'package:intl/intl.dart'; -import '../controllers/academic_fees_controller.dart'; -import '../controllers/auth_controller.dart'; -import '../models/academic_fee_model.dart'; -import '../views/payment_page.dart'; -import '../services/pdf_receipt_service.dart'; - -class AcademicFeesScreen extends StatefulWidget { - const AcademicFeesScreen({Key? key}) : super(key: key); - - @override - State createState() => _AcademicFeesScreenState(); -} - -class _AcademicFeesScreenState extends State - with TickerProviderStateMixin { - late AnimationController _headerAnimationController; - late AnimationController _pulseController; - late Animation _headerScaleAnimation; - late Animation _headerSlideAnimation; - late Animation _pulseAnimation; - - final AcademicFeesController feesController = Get.put( - AcademicFeesController(), - ); - final TextEditingController searchController = TextEditingController(); - - @override - void initState() { - super.initState(); - _setupAnimations(); - } - - void _setupAnimations() { - _headerAnimationController = AnimationController( - duration: const Duration(milliseconds: 1200), - vsync: this, - ); - - _pulseController = AnimationController( - duration: const Duration(milliseconds: 2000), - vsync: this, - )..repeat(reverse: true); - - _headerScaleAnimation = Tween(begin: 0.8, end: 1.0).animate( - CurvedAnimation( - parent: _headerAnimationController, - curve: Curves.elasticOut, - ), - ); - - _headerSlideAnimation = Tween( - begin: const Offset(0, -0.3), - end: Offset.zero, - ).animate( - CurvedAnimation( - parent: _headerAnimationController, - curve: Curves.easeOutBack, - ), - ); - - _pulseAnimation = Tween(begin: 1.0, end: 1.05).animate( - CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), - ); - - _headerAnimationController.forward(); - } - - @override - void dispose() { - _headerAnimationController.dispose(); - _pulseController.dispose(); - searchController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.grey[50], - body: CustomScrollView( - slivers: [ - _buildAnimatedAppBar(), - _buildStatisticsSection(), - _buildFiltersSection(), - _buildFeesListSection(), - ], - ), - ); - } - - Widget _buildAnimatedAppBar() { - return SliverAppBar( - expandedHeight: 120, - floating: false, - pinned: true, - backgroundColor: const Color(0xFF6366F1), - flexibleSpace: FlexibleSpaceBar( - title: AnimatedBuilder( - animation: _headerAnimationController, - builder: (context, child) { - return SlideTransition( - position: _headerSlideAnimation, - child: ScaleTransition( - scale: _headerScaleAnimation, - child: const Text( - 'Academic Fees', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), - ), - ), - ); - }, - ), - background: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)], - ), - ), - child: Stack( - children: [ - Positioned( - top: 20, - right: -20, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - shape: BoxShape.circle, - ), - ), - ), - Positioned( - bottom: -30, - left: -30, - child: Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), - shape: BoxShape.circle, - ), - ), - ), - ], - ), - ), - ), - actions: [ - Obx( - () => - feesController.overdueCount.value > 0 - ? AnimatedBuilder( - animation: _pulseAnimation, - builder: (context, child) { - return Transform.scale( - scale: _pulseAnimation.value, - child: IconButton( - icon: Stack( - children: [ - const Icon(Icons.warning_amber_rounded), - Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.all(2), - decoration: const BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - ), - constraints: const BoxConstraints( - minWidth: 16, - minHeight: 16, - ), - child: Text( - '${feesController.overdueCount.value}', - style: const TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), - ), - ], - ), - onPressed: () { - feesController.setFilter('overdue'); - HapticFeedback.lightImpact(); - }, - ), - ); - }, - ) - : const SizedBox(), - ), - IconButton( - icon: const Icon(Icons.refresh), - onPressed: () { - feesController.refreshFees(); - HapticFeedback.lightImpact(); - }, - ), - ], - ); - } - - Widget _buildStatisticsSection() { - return SliverToBoxAdapter( - child: Obx(() { - if (feesController.isLoading.value) { - return _buildStatisticsShimmer(); - } - - return AnimationLimiter( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: AnimationConfiguration.toStaggeredList( - duration: const Duration(milliseconds: 600), - childAnimationBuilder: - (widget) => SlideAnimation( - horizontalOffset: 50.0, - child: FadeInAnimation(child: widget), - ), - children: [ - Row( - children: [ - Expanded( - child: _buildStatCard( - 'Total Pending', - feesController.formatCurrency( - feesController.totalPending.value, - ), - Icons.pending_actions, - const Color(0xFF3B82F6), // Blue instead of orange - feesController.pendingCount.value + - feesController.overdueCount.value, - ), - ), - const SizedBox(width: 12), - Expanded( - child: _buildStatCard( - 'Total Paid', - feesController.formatCurrency( - feesController.totalPaid.value, - ), - Icons.check_circle, - const Color( - 0xFF1E40AF, - ), // Darker blue instead of green - feesController.paidCount.value, - ), - ), - ], - ), - ], - ), - ), - ), - ); - }), - ); - } - - Widget _buildStatCard( - String title, - String amount, - IconData icon, - Color color, - int count, - ) { - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.white, color.withOpacity(0.02)], - ), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withOpacity(0.1), width: 1), - boxShadow: [ - BoxShadow( - color: color.withOpacity(0.08), - blurRadius: 20, - offset: const Offset(0, 8), - spreadRadius: 0, - ), - BoxShadow( - color: Colors.black.withOpacity(0.02), - blurRadius: 10, - offset: const Offset(0, 2), - spreadRadius: 0, - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [color.withOpacity(0.15), color.withOpacity(0.05)], - ), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: color.withOpacity(0.2), width: 1), - ), - child: Icon(icon, color: color, size: 24), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [color.withOpacity(0.15), color.withOpacity(0.08)], - ), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withOpacity(0.3), width: 1), - ), - child: Text( - '$count', - style: TextStyle( - color: color, - fontWeight: FontWeight.bold, - fontSize: 13, - ), - ), - ), - ], - ), - const SizedBox(height: 16), - Text( - title, - style: TextStyle( - color: Colors.grey[600], - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 6), - Text( - amount, - style: TextStyle( - color: color, - fontSize: 20, - fontWeight: FontWeight.bold, - letterSpacing: -0.5, - ), - ), - ], - ), - ); - } - - Widget _buildStatisticsShimmer() { - return Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Expanded(child: _buildShimmerCard()), - const SizedBox(width: 12), - Expanded(child: _buildShimmerCard()), - ], - ), - ); - } - - Widget _buildShimmerCard() { - return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Container( - height: 100, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - ), - ), - ); - } - - Widget _buildFiltersSection() { - return SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - children: [ - // Search Bar - Container( - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: TextField( - controller: searchController, - decoration: InputDecoration( - hintText: 'Search fees...', - prefixIcon: const Icon( - Icons.search, - color: Color(0xFF6366F1), - ), - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 16, - ), // Adjusted padding to reduce space - suffixIcon: - searchController.text.isNotEmpty - ? IconButton( - icon: const Icon(Icons.clear), - onPressed: () { - searchController.clear(); - feesController.setSearchQuery(''); - }, - ) - : null, - ), - onChanged: (value) { - feesController.setSearchQuery(value); - }, - ), - ), - // Filter Chips - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Obx( - () => Row( - children: [ - _buildFilterChip('All', 'all'), - const SizedBox(width: 8), - _buildFilterChip('Pending', 'pending'), - const SizedBox(width: 8), - _buildFilterChip('Paid', 'paid'), - const SizedBox(width: 8), - _buildFilterChip('Overdue', 'overdue'), - ], - ), - ), - ), - ], - ), - ), - ); - } - - Widget _buildFilterChip(String label, String value) { - final isSelected = feesController.selectedFilter.value == value; - return FilterChip( - label: Text(label), - selected: isSelected, - onSelected: (selected) { - feesController.setFilter(value); - HapticFeedback.selectionClick(); - }, - backgroundColor: Colors.white, - selectedColor: const Color(0xFF6366F1).withOpacity(0.1), - checkmarkColor: const Color(0xFF6366F1), - labelStyle: TextStyle( - color: isSelected ? const Color(0xFF6366F1) : Colors.grey[600], - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - ), - side: BorderSide( - color: isSelected ? const Color(0xFF6366F1) : Colors.grey[300]!, - ), - ); - } - - Widget _buildFeesListSection() { - return Obx(() { - if (feesController.isLoading.value) { - return _buildLoadingList(); - } - - if (feesController.errorMessage.value.isNotEmpty) { - return _buildErrorState(); - } - - final filteredFees = feesController.filteredFees; - - if (filteredFees.isEmpty) { - return _buildEmptyState(); - } - - return SliverPadding( - padding: const EdgeInsets.all(16), - sliver: AnimationLimiter( - child: SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 600), - child: SlideAnimation( - verticalOffset: 50.0, - child: FadeInAnimation( - child: _buildFeeCard(filteredFees[index], index), - ), - ), - ); - }, childCount: filteredFees.length), - ), - ), - ); - }); - } - - Widget _buildFeeCard(AcademicFeeModel fee, int index) { - final statusColor = feesController.getStatusColor(fee.currentStatus); - final typeIcon = feesController.getTypeIcon(fee.type); - - return Container( - margin: const EdgeInsets.only(bottom: 16), - child: Material( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - elevation: fee.currentStatus == FeeStatus.overdue ? 8 : 4, - shadowColor: statusColor.withOpacity(0.3), - child: InkWell( - borderRadius: BorderRadius.circular(20), - onTap: () => _showFeeDetails(fee), - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: - fee.currentStatus == FeeStatus.overdue - ? Colors.red.withOpacity(0.3) - : Colors.transparent, - width: 2, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon(typeIcon, color: statusColor, size: 24), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - fee.purpose, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - const SizedBox(height: 4), - Text( - feesController.getTypeDisplayName(fee.type), - style: TextStyle( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - fee.currentStatus - .toString() - .split('.') - .last - .toUpperCase(), - style: TextStyle( - color: statusColor, - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ), - ], - ), - const SizedBox(height: 16), - if (fee.description != null) ...[ - Text( - fee.description!, - style: TextStyle(fontSize: 14, color: Colors.grey[600]), - ), - const SizedBox(height: 12), - ], - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Amount', - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - ), - ), - Text( - feesController.formatCurrency(fee.totalAmount), - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: statusColor, - ), - ), - ], - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Due Date', - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - ), - ), - Text( - DateFormat('MMM dd, yyyy').format(fee.deadline), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - if (fee.currentStatus != FeeStatus.paid) ...[ - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - fee.isOverdue ? 'Overdue' : 'Days Left', - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - ), - ), - Text( - fee.isOverdue - ? '${DateTime.now().difference(fee.deadline).inDays}d' - : '${fee.daysRemaining}d', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: fee.isOverdue ? Colors.red : statusColor, - ), - ), - ], - ), - ], - ], - ), - if (fee.currentStatus != FeeStatus.paid) ...[ - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () => _handlePayment(fee), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF3B82F6), // Blue color - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 4, - shadowColor: const Color(0xFF3B82F6).withOpacity(0.3), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.payment, size: 22), - const SizedBox(width: 8), - Text( - fee.isOverdue ? 'Pay Now (Overdue)' : 'Pay Now', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - ] else ...[ - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pop(context); - _handleReceiptGeneration(fee); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1E40AF), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.receipt_long, size: 20), - const SizedBox(width: 8), - const Text( - 'Show Receipt', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - const SizedBox(height: 12), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pop(context); - _handleEmailReceipt(fee); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF10B981), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.email, size: 20), - const SizedBox(width: 8), - const Text( - 'Send Email Receipt', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - ], - ], - ), - ), - ), - ), - ); - } - - Widget _buildLoadingList() { - return SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Container( - height: 180, - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - ), - ); - }, childCount: 5), - ), - ); - } - - Widget _buildErrorState() { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), - const SizedBox(height: 16), - Text( - 'Failed to load fees', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Text( - feesController.errorMessage.value, - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey[500]), - ), - const SizedBox(height: 24), - ElevatedButton.icon( - onPressed: () => feesController.refreshFees(), - icon: const Icon(Icons.refresh), - label: const Text('Retry'), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF3B82F6), - foregroundColor: Colors.white, - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildEmptyState() { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Icon( - Icons.receipt_long_outlined, - size: 64, - color: Colors.grey[400], - ), - const SizedBox(height: 16), - Text( - 'No fees found', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Text( - 'No academic fees match your current filter', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey[500]), - ), - ], - ), - ), - ), - ); - } - - void _showFeeDetails(AcademicFeeModel fee) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => _buildFeeDetailsSheet(fee), - ); - } - - Widget _buildFeeDetailsSheet(AcademicFeeModel fee) { - return Container( - height: MediaQuery.of(context).size.height * 0.85, - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), - ), - child: Column( - children: [ - // Handle - Container( - margin: const EdgeInsets.only(top: 12), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(2), - ), - ), - // Content - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: feesController - .getStatusColor(fee.currentStatus) - .withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - feesController.getTypeIcon(fee.type), - color: feesController.getStatusColor( - fee.currentStatus, - ), - size: 28, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - fee.purpose, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Text( - feesController.getTypeDisplayName(fee.type), - style: TextStyle( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 20), - // Fee breakdown section - if (fee.items.isNotEmpty) ...[ - const Text( - 'Fee Breakdown', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - const SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey[200]!), - ), - child: Column( - children: [ - ...fee.items.asMap().entries.map((entry) { - final index = entry.key; - final item = entry.value; - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - border: - index < fee.items.length - 1 - ? Border( - bottom: BorderSide( - color: Colors.grey[200]!, - width: 1, - ), - ) - : null, - ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - item.name, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - if (item.description != null) ...[ - const SizedBox(height: 2), - Text( - item.name, - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ], - ), - ), - const SizedBox(width: 8), - Text( - feesController.formatCurrency(item.amount), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: feesController.getStatusColor( - fee.currentStatus, - ), - ), - ), - ], - ), - ); - }).toList(), - // Subtotal - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(12), - bottomRight: Radius.circular(12), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Subtotal', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - Text( - feesController.formatCurrency(fee.amount), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 12), - ] else ...[ - _buildDetailRow( - 'Amount', - feesController.formatCurrency(fee.amount), - ), - ], - if (fee.lateFee != null && fee.isOverdue) - _buildDetailRow( - 'Late Fee', - feesController.formatCurrency(fee.lateFee!), - ), - if (fee.discount != null) - _buildDetailRow( - 'Discount', - '-${feesController.formatCurrency(fee.discount!)}', - ), - const Divider(height: 20), - _buildDetailRow( - 'Total', - feesController.formatCurrency(fee.totalAmount), - isTotal: true, - ), - const SizedBox(height: 12), - _buildDetailRow( - 'Due Date', - DateFormat('MMMM dd, yyyy').format(fee.deadline), - ), - _buildDetailRow('Academic Year', fee.academicYear), - _buildDetailRow('Semester', fee.semester), - if (fee.description != null) - _buildDetailRow('Description', fee.description!), - if (fee.status == FeeStatus.paid) ...[ - const SizedBox(height: 12), - _buildDetailRow( - 'Payment Date', - DateFormat('MMMM dd, yyyy').format(fee.paidAt!), - ), - _buildDetailRow('Receipt Number', fee.receiptNumber!), - ], - const SizedBox(height: 80), // Space for button - ], - ), - ), - ), - // Payment Button (Fixed at bottom) - if (fee.currentStatus != FeeStatus.paid) - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, -2), - ), - ], - ), - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pop(context); - Get.to(() => PaymentPage(fee: fee)); - }, - style: ElevatedButton.styleFrom( - backgroundColor: feesController.getStatusColor( - fee.currentStatus, - ), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text( - 'Pay ${feesController.formatCurrency(fee.totalAmount)}', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ) - else - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, -2), - ), - ], - ), - child: Column( - children: [ - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pop(context); - _handleReceiptGeneration(fee); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF1E40AF), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.receipt_long, size: 20), - const SizedBox(width: 8), - const Text( - 'Show Receipt', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - const SizedBox(height: 12), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pop(context); - _handleEmailReceipt(fee); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF10B981), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.email, size: 20), - const SizedBox(width: 8), - const Text( - 'Send Email Receipt', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildDetailRow(String label, String value, {bool isTotal = false}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 100, - child: Text( - label, - style: TextStyle( - color: Colors.grey[600], - fontSize: isTotal ? 14 : 13, - fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, - ), - ), - ), - Expanded( - child: Text( - value, - style: TextStyle( - fontSize: isTotal ? 16 : 13, - fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, - color: isTotal ? const Color(0xFF6366F1) : Colors.black87, - ), - ), - ), - ], - ), - ); - } - - void _handlePayment(AcademicFeeModel fee) { - HapticFeedback.mediumImpact(); - Get.to(() => PaymentPage(fee: fee)); - } - - void _handleReceiptGeneration(AcademicFeeModel fee) async { - final authController = Get.find(); - if (authController.user == null) { - Get.snackbar( - 'Error', - 'User information not available', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - return; - } - - HapticFeedback.lightImpact(); - await feesController.generateReceipt(fee, authController.user!); - } - - void _handleEmailReceipt(AcademicFeeModel fee) async { - final authController = Get.find(); - if (authController.user == null) { - Get.snackbar( - 'Error', - 'User information not available', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - return; - } - - HapticFeedback.lightImpact(); - - try { - await PdfReceiptService.generateAndEmailReceipt( - fee: fee, - user: authController.user!, - ); - Get.snackbar( - 'Success', - 'Payment confirmation email sent successfully', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: const Color(0xFF10B981), - colorText: Colors.white, - ); - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to send email: $e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - } - } -} +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:intl/intl.dart'; +import '../controllers/academic_fees_controller.dart'; +import '../controllers/auth_controller.dart'; +import '../models/academic_fee_model.dart'; +import '../views/payment_page.dart'; +import '../services/pdf_receipt_service.dart'; + +class AcademicFeesScreen extends StatefulWidget { + const AcademicFeesScreen({Key? key}) : super(key: key); + + @override + State createState() => _AcademicFeesScreenState(); +} + +class _AcademicFeesScreenState extends State + with TickerProviderStateMixin { + late AnimationController _headerAnimationController; + late AnimationController _pulseController; + late Animation _headerScaleAnimation; + late Animation _headerSlideAnimation; + late Animation _pulseAnimation; + + final AcademicFeesController feesController = Get.put( + AcademicFeesController(), + ); + final TextEditingController searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + _setupAnimations(); + } + + void _setupAnimations() { + _headerAnimationController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + + _pulseController = AnimationController( + duration: const Duration(milliseconds: 2000), + vsync: this, + )..repeat(reverse: true); + + _headerScaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation( + parent: _headerAnimationController, + curve: Curves.elasticOut, + ), + ); + + _headerSlideAnimation = Tween( + begin: const Offset(0, -0.3), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _headerAnimationController, + curve: Curves.easeOutBack, + ), + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.05).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + + _headerAnimationController.forward(); + } + + @override + void dispose() { + _headerAnimationController.dispose(); + _pulseController.dispose(); + searchController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey[50], + body: CustomScrollView( + slivers: [ + _buildAnimatedAppBar(), + _buildStatisticsSection(), + _buildFiltersSection(), + _buildFeesListSection(), + ], + ), + ); + } + + Widget _buildAnimatedAppBar() { + return SliverAppBar( + expandedHeight: 120, + floating: false, + pinned: true, + backgroundColor: const Color(0xFF6366F1), + flexibleSpace: FlexibleSpaceBar( + title: AnimatedBuilder( + animation: _headerAnimationController, + builder: (context, child) { + return SlideTransition( + position: _headerSlideAnimation, + child: ScaleTransition( + scale: _headerScaleAnimation, + child: const Text( + 'Academic Fees', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), + ), + ), + ); + }, + ), + background: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)], + ), + ), + child: Stack( + children: [ + Positioned( + top: 20, + right: -20, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + shape: BoxShape.circle, + ), + ), + ), + Positioned( + bottom: -30, + left: -30, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.05), + shape: BoxShape.circle, + ), + ), + ), + ], + ), + ), + ), + actions: [ + Obx( + () => + feesController.overdueCount.value > 0 + ? AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _pulseAnimation.value, + child: IconButton( + icon: Stack( + children: [ + const Icon(Icons.warning_amber_rounded), + Positioned( + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 16, + minHeight: 16, + ), + child: Text( + '${feesController.overdueCount.value}', + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + onPressed: () { + feesController.setFilter('overdue'); + HapticFeedback.lightImpact(); + }, + ), + ); + }, + ) + : const SizedBox(), + ), + IconButton( + icon: const Icon(Icons.refresh), + onPressed: () { + feesController.refreshFees(); + HapticFeedback.lightImpact(); + }, + ), + ], + ); + } + + Widget _buildStatisticsSection() { + return SliverToBoxAdapter( + child: Obx(() { + if (feesController.isLoading.value) { + return _buildStatisticsShimmer(); + } + + return AnimationLimiter( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: AnimationConfiguration.toStaggeredList( + duration: const Duration(milliseconds: 600), + childAnimationBuilder: + (widget) => SlideAnimation( + horizontalOffset: 50.0, + child: FadeInAnimation(child: widget), + ), + children: [ + Row( + children: [ + Expanded( + child: _buildStatCard( + 'Total Pending', + feesController.formatCurrency( + feesController.totalPending.value, + ), + Icons.pending_actions, + const Color(0xFF3B82F6), // Blue instead of orange + feesController.pendingCount.value + + feesController.overdueCount.value, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildStatCard( + 'Total Paid', + feesController.formatCurrency( + feesController.totalPaid.value, + ), + Icons.check_circle, + const Color( + 0xFF1E40AF, + ), // Darker blue instead of green + feesController.paidCount.value, + ), + ), + ], + ), + ], + ), + ), + ), + ); + }), + ); + } + + Widget _buildStatCard( + String title, + String amount, + IconData icon, + Color color, + int count, + ) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.white, color.withOpacity(0.02)], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color.withOpacity(0.1), width: 1), + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.08), + blurRadius: 20, + offset: const Offset(0, 8), + spreadRadius: 0, + ), + BoxShadow( + color: Colors.black.withOpacity(0.02), + blurRadius: 10, + offset: const Offset(0, 2), + spreadRadius: 0, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [color.withOpacity(0.15), color.withOpacity(0.05)], + ), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: color.withOpacity(0.2), width: 1), + ), + child: Icon(icon, color: color, size: 24), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [color.withOpacity(0.15), color.withOpacity(0.08)], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color.withOpacity(0.3), width: 1), + ), + child: Text( + '$count', + style: TextStyle( + color: color, + fontWeight: FontWeight.bold, + fontSize: 13, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + Text( + title, + style: TextStyle( + color: Colors.grey[600], + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 6), + Text( + amount, + style: TextStyle( + color: color, + fontSize: 20, + fontWeight: FontWeight.bold, + letterSpacing: -0.5, + ), + ), + ], + ), + ); + } + + Widget _buildStatisticsShimmer() { + return Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded(child: _buildShimmerCard()), + const SizedBox(width: 12), + Expanded(child: _buildShimmerCard()), + ], + ), + ); + } + + Widget _buildShimmerCard() { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 100, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + ), + ); + } + + Widget _buildFiltersSection() { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + children: [ + // Search Bar + Container( + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: TextField( + controller: searchController, + decoration: InputDecoration( + hintText: 'Search fees...', + prefixIcon: const Icon( + Icons.search, + color: Color(0xFF6366F1), + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 16, + ), // Adjusted padding to reduce space + suffixIcon: + searchController.text.isNotEmpty + ? IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + searchController.clear(); + feesController.setSearchQuery(''); + }, + ) + : null, + ), + onChanged: (value) { + feesController.setSearchQuery(value); + }, + ), + ), + // Filter Chips + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Obx( + () => Row( + children: [ + _buildFilterChip('All', 'all'), + const SizedBox(width: 8), + _buildFilterChip('Pending', 'pending'), + const SizedBox(width: 8), + _buildFilterChip('Paid', 'paid'), + const SizedBox(width: 8), + _buildFilterChip('Overdue', 'overdue'), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildFilterChip(String label, String value) { + final isSelected = feesController.selectedFilter.value == value; + return FilterChip( + label: Text(label), + selected: isSelected, + onSelected: (selected) { + feesController.setFilter(value); + HapticFeedback.selectionClick(); + }, + backgroundColor: Colors.white, + selectedColor: const Color(0xFF6366F1).withOpacity(0.1), + checkmarkColor: const Color(0xFF6366F1), + labelStyle: TextStyle( + color: isSelected ? const Color(0xFF6366F1) : Colors.grey[600], + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + side: BorderSide( + color: isSelected ? const Color(0xFF6366F1) : Colors.grey[300]!, + ), + ); + } + + Widget _buildFeesListSection() { + return Obx(() { + if (feesController.isLoading.value) { + return _buildLoadingList(); + } + + if (feesController.errorMessage.value.isNotEmpty) { + return _buildErrorState(); + } + + final filteredFees = feesController.filteredFees; + + if (filteredFees.isEmpty) { + return _buildEmptyState(); + } + + return SliverPadding( + padding: const EdgeInsets.all(16), + sliver: AnimationLimiter( + child: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 600), + child: SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: _buildFeeCard(filteredFees[index], index), + ), + ), + ); + }, childCount: filteredFees.length), + ), + ), + ); + }); + } + + Widget _buildFeeCard(AcademicFeeModel fee, int index) { + final statusColor = feesController.getStatusColor(fee.currentStatus); + final typeIcon = feesController.getTypeIcon(fee.type); + + return Container( + margin: const EdgeInsets.only(bottom: 16), + child: Material( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + elevation: fee.currentStatus == FeeStatus.overdue ? 8 : 4, + shadowColor: statusColor.withOpacity(0.3), + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () => _showFeeDetails(fee), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: + fee.currentStatus == FeeStatus.overdue + ? Colors.red.withOpacity(0.3) + : Colors.transparent, + width: 2, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(typeIcon, color: statusColor, size: 24), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + fee.purpose, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 4), + Text( + feesController.getTypeDisplayName(fee.type), + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + fee.currentStatus + .toString() + .split('.') + .last + .toUpperCase(), + style: TextStyle( + color: statusColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + if (fee.description != null) ...[ + Text( + fee.description!, + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + const SizedBox(height: 12), + ], + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Amount', + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + Text( + feesController.formatCurrency(fee.totalAmount), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: statusColor, + ), + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Due Date', + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + Text( + DateFormat('MMM dd, yyyy').format(fee.deadline), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + if (fee.currentStatus != FeeStatus.paid) ...[ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + fee.isOverdue ? 'Overdue' : 'Days Left', + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + Text( + fee.isOverdue + ? '${DateTime.now().difference(fee.deadline).inDays}d' + : '${fee.daysRemaining}d', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: fee.isOverdue ? Colors.red : statusColor, + ), + ), + ], + ), + ], + ], + ), + if (fee.currentStatus != FeeStatus.paid) ...[ + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => _handlePayment(fee), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF3B82F6), // Blue color + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 4, + shadowColor: const Color(0xFF3B82F6).withOpacity(0.3), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.payment, size: 22), + const SizedBox(width: 8), + Text( + fee.isOverdue ? 'Pay Now (Overdue)' : 'Pay Now', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ] else ...[ + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + _handleReceiptGeneration(fee); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF1E40AF), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.receipt_long, size: 20), + const SizedBox(width: 8), + const Text( + 'Show Receipt', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + _handleEmailReceipt(fee); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF10B981), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.email, size: 20), + const SizedBox(width: 8), + const Text( + 'Send Email Receipt', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ], + ), + ), + ), + ), + ); + } + + Widget _buildLoadingList() { + return SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 180, + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + ), + ); + }, childCount: 5), + ), + ); + } + + Widget _buildErrorState() { + return SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + 'Failed to load fees', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 8), + Text( + feesController.errorMessage.value, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[500]), + ), + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: () => feesController.refreshFees(), + icon: const Icon(Icons.refresh), + label: const Text('Retry'), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF3B82F6), + foregroundColor: Colors.white, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildEmptyState() { + return SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + Icon( + Icons.receipt_long_outlined, + size: 64, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'No fees found', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 8), + Text( + 'No academic fees match your current filter', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[500]), + ), + ], + ), + ), + ), + ); + } + + void _showFeeDetails(AcademicFeeModel fee) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => _buildFeeDetailsSheet(fee), + ); + } + + Widget _buildFeeDetailsSheet(AcademicFeeModel fee) { + return Container( + height: MediaQuery.of(context).size.height * 0.85, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Handle + Container( + margin: const EdgeInsets.only(top: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + // Content + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: feesController + .getStatusColor(fee.currentStatus) + .withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + feesController.getTypeIcon(fee.type), + color: feesController.getStatusColor( + fee.currentStatus, + ), + size: 28, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + fee.purpose, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + feesController.getTypeDisplayName(fee.type), + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 20), + // Fee breakdown section + if (fee.items.isNotEmpty) ...[ + const Text( + 'Fee Breakdown', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + children: [ + ...fee.items.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: + index < fee.items.length - 1 + ? Border( + bottom: BorderSide( + color: Colors.grey[200]!, + width: 1, + ), + ) + : null, + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + item.name, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + if (item.description != null) ...[ + const SizedBox(height: 2), + Text( + item.name, + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ], + ), + ), + const SizedBox(width: 8), + Text( + feesController.formatCurrency(item.amount), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: feesController.getStatusColor( + fee.currentStatus, + ), + ), + ), + ], + ), + ); + }).toList(), + // Subtotal + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Subtotal', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text( + feesController.formatCurrency(fee.amount), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 12), + ] else ...[ + _buildDetailRow( + 'Amount', + feesController.formatCurrency(fee.amount), + ), + ], + if (fee.lateFee != null && fee.isOverdue) + _buildDetailRow( + 'Late Fee', + feesController.formatCurrency(fee.lateFee!), + ), + if (fee.discount != null) + _buildDetailRow( + 'Discount', + '-${feesController.formatCurrency(fee.discount!)}', + ), + const Divider(height: 20), + _buildDetailRow( + 'Total', + feesController.formatCurrency(fee.totalAmount), + isTotal: true, + ), + const SizedBox(height: 12), + _buildDetailRow( + 'Due Date', + DateFormat('MMMM dd, yyyy').format(fee.deadline), + ), + _buildDetailRow('Academic Year', fee.academicYear), + _buildDetailRow('Semester', fee.semester), + if (fee.description != null) + _buildDetailRow('Description', fee.description!), + if (fee.status == FeeStatus.paid) ...[ + const SizedBox(height: 12), + _buildDetailRow( + 'Payment Date', + DateFormat('MMMM dd, yyyy').format(fee.paidAt!), + ), + _buildDetailRow('Receipt Number', fee.receiptNumber!), + ], + const SizedBox(height: 80), // Space for button + ], + ), + ), + ), + // Payment Button (Fixed at bottom) + if (fee.currentStatus != FeeStatus.paid) + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, -2), + ), + ], + ), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + Get.to(() => PaymentPage(fee: fee)); + }, + style: ElevatedButton.styleFrom( + backgroundColor: feesController.getStatusColor( + fee.currentStatus, + ), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Pay ${feesController.formatCurrency(fee.totalAmount)}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ) + else + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, -2), + ), + ], + ), + child: Column( + children: [ + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + _handleReceiptGeneration(fee); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF1E40AF), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.receipt_long, size: 20), + const SizedBox(width: 8), + const Text( + 'Show Receipt', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + _handleEmailReceipt(fee); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF10B981), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.email, size: 20), + const SizedBox(width: 8), + const Text( + 'Send Email Receipt', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildDetailRow(String label, String value, {bool isTotal = false}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 100, + child: Text( + label, + style: TextStyle( + color: Colors.grey[600], + fontSize: isTotal ? 14 : 13, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: isTotal ? 16 : 13, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + color: isTotal ? const Color(0xFF6366F1) : Colors.black87, + ), + ), + ), + ], + ), + ); + } + + void _handlePayment(AcademicFeeModel fee) { + HapticFeedback.mediumImpact(); + Get.to(() => PaymentPage(fee: fee)); + } + + void _handleReceiptGeneration(AcademicFeeModel fee) async { + final authController = Get.find(); + if (authController.user == null) { + Get.snackbar( + 'Error', + 'User information not available', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + HapticFeedback.lightImpact(); + await feesController.generateReceipt(fee, authController.user!); + } + + void _handleEmailReceipt(AcademicFeeModel fee) async { + final authController = Get.find(); + if (authController.user == null) { + Get.snackbar( + 'Error', + 'User information not available', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + HapticFeedback.lightImpact(); + + try { + await PdfReceiptService.generateAndEmailReceipt( + fee: fee, + user: authController.user!, + ); + Get.snackbar( + 'Success', + 'Payment confirmation email sent successfully', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: const Color(0xFF10B981), + colorText: Colors.white, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to send email: $e', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } +} diff --git a/lib/views/academic_fees_screen_backup.dart b/lib/views/academic_fees_screen_backup.dart index 485b5e9..83134e0 100644 --- a/lib/views/academic_fees_screen_backup.dart +++ b/lib/views/academic_fees_screen_backup.dart @@ -1,1168 +1,1168 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:shimmer/shimmer.dart'; -import 'package:intl/intl.dart'; -import '../controllers/academic_fees_controller.dart'; -import '../models/academic_fee_model.dart'; - -class AcademicFeesScreen extends StatefulWidget { - const AcademicFeesScreen({Key? key}) : super(key: key); - - @override - State createState() => _AcademicFeesScreenState(); -} - -class _AcademicFeesScreenState extends State - with TickerProviderStateMixin { - late AnimationController _headerAnimationController; - late AnimationController _pulseController; - late Animation _headerScaleAnimation; - late Animation _headerSlideAnimation; - late Animation _pulseAnimation; - - final AcademicFeesController feesController = Get.put( - AcademicFeesController(), - ); - final TextEditingController searchController = TextEditingController(); - - @override - void initState() { - super.initState(); - _setupAnimations(); - } - - void _setupAnimations() { - _headerAnimationController = AnimationController( - duration: const Duration(milliseconds: 1200), - vsync: this, - ); - - _pulseController = AnimationController( - duration: const Duration(milliseconds: 2000), - vsync: this, - )..repeat(reverse: true); - - _headerScaleAnimation = Tween(begin: 0.8, end: 1.0).animate( - CurvedAnimation( - parent: _headerAnimationController, - curve: Curves.elasticOut, - ), - ); - - _headerSlideAnimation = Tween( - begin: const Offset(0, -0.3), - end: Offset.zero, - ).animate( - CurvedAnimation( - parent: _headerAnimationController, - curve: Curves.easeOutBack, - ), - ); - - _pulseAnimation = Tween(begin: 1.0, end: 1.05).animate( - CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), - ); - - _headerAnimationController.forward(); - } - - @override - void dispose() { - _headerAnimationController.dispose(); - _pulseController.dispose(); - searchController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Colors.grey[50], - body: CustomScrollView( - slivers: [ - _buildAnimatedAppBar(), - _buildStatisticsSection(), - _buildFiltersSection(), - _buildFeesListSection(), - ], - ), - ); - } - - Widget _buildAnimatedAppBar() { - return SliverAppBar( - expandedHeight: 120, - floating: false, - pinned: true, - backgroundColor: const Color(0xFF6366F1), - flexibleSpace: FlexibleSpaceBar( - title: AnimatedBuilder( - animation: _headerAnimationController, - builder: (context, child) { - return SlideTransition( - position: _headerSlideAnimation, - child: ScaleTransition( - scale: _headerScaleAnimation, - child: const Text( - 'Academic Fees', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), - ), - ), - ); - }, - ), - background: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)], - ), - ), - child: Stack( - children: [ - Positioned( - top: 20, - right: -20, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - shape: BoxShape.circle, - ), - ), - ), - Positioned( - bottom: -30, - left: -30, - child: Container( - width: 80, - height: 80, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), - shape: BoxShape.circle, - ), - ), - ), - ], - ), - ), - ), - actions: [ - Obx( - () => - feesController.overdueCount.value > 0 - ? AnimatedBuilder( - animation: _pulseAnimation, - builder: (context, child) { - return Transform.scale( - scale: _pulseAnimation.value, - child: IconButton( - icon: Stack( - children: [ - const Icon(Icons.warning_amber_rounded), - Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.all(2), - decoration: const BoxDecoration( - color: Colors.red, - shape: BoxShape.circle, - ), - constraints: const BoxConstraints( - minWidth: 16, - minHeight: 16, - ), - child: Text( - '${feesController.overdueCount.value}', - style: const TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), - ), - ], - ), - onPressed: () { - feesController.setFilter('overdue'); - HapticFeedback.lightImpact(); - }, - ), - ); - }, - ) - : const SizedBox(), - ), - IconButton( - icon: const Icon(Icons.refresh), - onPressed: () { - feesController.refreshFees(); - HapticFeedback.lightImpact(); - }, - ), - ], - ); - } - - Widget _buildStatisticsSection() { - return SliverToBoxAdapter( - child: Obx(() { - if (feesController.isLoading.value) { - return _buildStatisticsShimmer(); - } - - return Padding( - padding: const EdgeInsets.all(16), - child: AnimationLimiter( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: AnimationConfiguration.toStaggeredList( - duration: const Duration(milliseconds: 600), - childAnimationBuilder: - (widget) => SlideAnimation( - horizontalOffset: 50.0, - child: FadeInAnimation(child: widget), - ), - children: [ - Expanded( - child: _buildStatCard( - 'Total Pending', - feesController.formatCurrency( - feesController.totalPending.value, - ), - Icons.pending_actions, - const Color(0xFF3B82F6), // Changed to blue - feesController.pendingCount.value + - feesController.overdueCount.value, - ), - ), - const SizedBox(width: 12), - Expanded( - child: _buildStatCard( - 'Total Paid', - feesController.formatCurrency( - feesController.totalPaid.value, - ), - Icons.check_circle, - const Color(0xFF1E40AF), // Changed to darker blue - feesController.paidCount.value, - ), - ), - ], - ), - ), - ), - ); - }), - ); - } - - Widget _buildStatCard( - String title, - String amount, - IconData icon, - Color color, - int count, - ) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: color.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, color: color, size: 20), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - '$count', - style: TextStyle( - color: color, - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ), - ], - ), - const SizedBox(height: 12), - Text(title, style: TextStyle(color: Colors.grey[600], fontSize: 14)), - const SizedBox(height: 4), - Text( - amount, - style: TextStyle( - color: color, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ); - } - - Widget _buildStatisticsShimmer() { - return Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - Expanded(child: _buildShimmerCard()), - const SizedBox(width: 12), - Expanded(child: _buildShimmerCard()), - ], - ), - ); - } - - Widget _buildShimmerCard() { - return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Container( - height: 100, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - ), - ), - ); - } - - Widget _buildFiltersSection() { - return SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), // Removed top padding - child: Column( - children: [ - // Search Bar - Container( - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: TextField( - controller: searchController, - decoration: InputDecoration( - hintText: 'Search fees...', - prefixIcon: const Icon( - Icons.search, - color: Color(0xFF6366F1), - ), - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 16, - ), - suffixIcon: Obx( - () => - searchController.text.isNotEmpty - ? IconButton( - icon: const Icon( - Icons.clear, - color: Color(0xFF6366F1), - ), - onPressed: () { - searchController.clear(); - feesController.setSearchQuery(''); - }, - ) - : const SizedBox(), - ), - ), - onChanged: (value) { - feesController.setSearchQuery(value); - }, - ), - ), - // Filter Chips - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Obx( - () => Row( - children: [ - _buildFilterChip('All', 'all'), - const SizedBox(width: 8), - _buildFilterChip('Pending', 'pending'), - const SizedBox(width: 8), - _buildFilterChip('Paid', 'paid'), - const SizedBox(width: 8), - _buildFilterChip('Overdue', 'overdue'), - ], - ), - ), - ), - ], - ), - ), - ); - } - - Widget _buildFilterChip(String label, String value) { - final isSelected = feesController.selectedFilter.value == value; - return FilterChip( - label: Text(label), - selected: isSelected, - onSelected: (selected) { - feesController.setFilter(value); - HapticFeedback.selectionClick(); - }, - backgroundColor: Colors.white, - selectedColor: const Color(0xFF6366F1).withOpacity(0.1), - checkmarkColor: const Color(0xFF6366F1), - labelStyle: TextStyle( - color: isSelected ? const Color(0xFF6366F1) : Colors.grey[600], - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, - ), - side: BorderSide( - color: isSelected ? const Color(0xFF6366F1) : Colors.grey[300]!, - ), - ); - } - - Widget _buildFeesListSection() { - return Obx(() { - if (feesController.isLoading.value) { - return _buildLoadingList(); - } - - if (feesController.errorMessage.value.isNotEmpty) { - return _buildErrorState(); - } - - final filteredFees = feesController.filteredFees; - - if (filteredFees.isEmpty) { - return _buildEmptyState(); - } - - return SliverPadding( - padding: const EdgeInsets.all(16), - sliver: AnimationLimiter( - child: SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 600), - child: SlideAnimation( - verticalOffset: 50.0, - child: FadeInAnimation( - child: _buildFeeCard(filteredFees[index], index), - ), - ), - ); - }, childCount: filteredFees.length), - ), - ), - ); - }); - } - - Widget _buildFeeCard(AcademicFeeModel fee, int index) { - final statusColor = feesController.getStatusColor(fee.currentStatus); - final typeIcon = feesController.getTypeIcon(fee.type); - - return Container( - margin: const EdgeInsets.only(bottom: 16), - child: Material( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - elevation: fee.currentStatus == FeeStatus.overdue ? 8 : 4, - shadowColor: statusColor.withOpacity(0.3), - child: InkWell( - borderRadius: BorderRadius.circular(20), - onTap: () => _showFeeDetails(fee), - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: - fee.currentStatus == FeeStatus.overdue - ? Colors.red.withOpacity(0.3) - : Colors.transparent, - width: 2, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon(typeIcon, color: statusColor, size: 24), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - fee.purpose, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - const SizedBox(height: 4), - Text( - feesController.getTypeDisplayName(fee.type), - style: TextStyle( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: statusColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - fee.currentStatus - .toString() - .split('.') - .last - .toUpperCase(), - style: TextStyle( - color: statusColor, - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ), - ], - ), - const SizedBox(height: 16), - if (fee.description != null) ...[ - Text( - fee.description!, - style: TextStyle(fontSize: 14, color: Colors.grey[600]), - ), - const SizedBox(height: 12), - ], - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Amount', - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - ), - ), - Text( - feesController.formatCurrency(fee.totalAmount), - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: statusColor, - ), - ), - ], - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Due Date', - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - ), - ), - Text( - DateFormat('MMM dd, yyyy').format(fee.deadline), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - if (fee.currentStatus != FeeStatus.paid) ...[ - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - fee.isOverdue ? 'Overdue' : 'Days Left', - style: TextStyle( - fontSize: 12, - color: Colors.grey[500], - ), - ), - Text( - fee.isOverdue - ? '${DateTime.now().difference(fee.deadline).inDays}d' - : '${fee.daysRemaining}d', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: fee.isOverdue ? Colors.red : statusColor, - ), - ), - ], - ), - ], - ], - ), - if (fee.currentStatus != FeeStatus.paid) ...[ - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () => _handlePayment(fee), - style: ElevatedButton.styleFrom( - backgroundColor: const Color( - 0xFF3B82F6, - ), // Changed to blue - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - elevation: 2, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.payment, size: 20), - const SizedBox(width: 8), - Text( - fee.isOverdue ? 'Pay Now (Overdue)' : 'Pay Now', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ), - ], - ], - ), - ), - ), - ), - ); - } - - Widget _buildLoadingList() { - return SliverPadding( - padding: const EdgeInsets.all(16), - sliver: SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Container( - height: 180, - margin: const EdgeInsets.only(bottom: 16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - ), - ), - ); - }, childCount: 5), - ), - ); - } - - Widget _buildErrorState() { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), - const SizedBox(height: 16), - Text( - 'Failed to load fees', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Text( - feesController.errorMessage.value, - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey[500]), - ), - const SizedBox(height: 24), - ElevatedButton.icon( - onPressed: () => feesController.refreshFees(), - icon: const Icon(Icons.refresh), - label: const Text('Retry'), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF6366F1), - foregroundColor: Colors.white, - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildEmptyState() { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Icon( - Icons.receipt_long_outlined, - size: 64, - color: Colors.grey[400], - ), - const SizedBox(height: 16), - Text( - 'No fees found', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Text( - 'No academic fees match your current filter', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey[500]), - ), - ], - ), - ), - ), - ); - } - - void _showFeeDetails(AcademicFeeModel fee) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => _buildFeeDetailsSheet(fee), - ); - } - - Widget _buildFeeDetailsSheet(AcademicFeeModel fee) { - return Container( - height: MediaQuery.of(context).size.height * 0.85, - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), - ), - child: Column( - children: [ - // Handle - Container( - margin: const EdgeInsets.only(top: 12), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(2), - ), - ), - // Content - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: feesController - .getStatusColor(fee.currentStatus) - .withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - feesController.getTypeIcon(fee.type), - color: feesController.getStatusColor( - fee.currentStatus, - ), - size: 28, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - fee.purpose, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Text( - feesController.getTypeDisplayName(fee.type), - style: TextStyle( - fontSize: 14, - color: Colors.grey[600], - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 20), - // Fee breakdown section - if (fee.items.isNotEmpty) ...[ - const Text( - 'Fee Breakdown', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - const SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey[200]!), - ), - child: Column( - children: [ - ...fee.items.asMap().entries.map((entry) { - final index = entry.key; - final item = entry.value; - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - border: - index < fee.items.length - 1 - ? Border( - bottom: BorderSide( - color: Colors.grey[200]!, - width: 1, - ), - ) - : null, - ), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - item.name, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - if (item.description != null) ...[ - const SizedBox(height: 2), - Text( - item.name, - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ], - ), - ), - const SizedBox(width: 8), - Text( - feesController.formatCurrency(item.amount), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: feesController.getStatusColor( - fee.currentStatus, - ), - ), - ), - ], - ), - ); - }).toList(), - // Subtotal - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(12), - bottomRight: Radius.circular(12), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Subtotal', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - Text( - feesController.formatCurrency(fee.amount), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - ], - ), - ), - ], - ), - ), - const SizedBox(height: 12), - ] else ...[ - _buildDetailRow( - 'Amount', - feesController.formatCurrency(fee.amount), - ), - ], - if (fee.lateFee != null && fee.isOverdue) - _buildDetailRow( - 'Late Fee', - feesController.formatCurrency(fee.lateFee!), - ), - if (fee.discount != null) - _buildDetailRow( - 'Discount', - '-${feesController.formatCurrency(fee.discount!)}', - ), - const Divider(height: 20), - _buildDetailRow( - 'Total', - feesController.formatCurrency(fee.totalAmount), - isTotal: true, - ), - const SizedBox(height: 12), - _buildDetailRow( - 'Due Date', - DateFormat('MMMM dd, yyyy').format(fee.deadline), - ), - _buildDetailRow('Academic Year', fee.academicYear), - _buildDetailRow('Semester', fee.semester), - if (fee.description != null) - _buildDetailRow('Description', fee.description!), - if (fee.status == FeeStatus.paid) ...[ - const SizedBox(height: 12), - _buildDetailRow( - 'Payment Date', - DateFormat('MMMM dd, yyyy').format(fee.paidAt!), - ), - _buildDetailRow('Receipt Number', fee.receiptNumber!), - ], - const SizedBox(height: 80), // Space for button - ], - ), - ), - ), - // Payment Button (Fixed at bottom) - if (fee.currentStatus != FeeStatus.paid) - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, -2), - ), - ], - ), - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pop(context); - _handlePayment(fee); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF3B82F6), // Changed to blue - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text( - 'Pay ${feesController.formatCurrency(fee.totalAmount)}', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ], - ), - ); - } - - Widget _buildDetailRow(String label, String value, {bool isTotal = false}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 100, - child: Text( - label, - style: TextStyle( - color: Colors.grey[600], - fontSize: isTotal ? 14 : 13, - fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, - ), - ), - ), - Expanded( - child: Text( - value, - style: TextStyle( - fontSize: isTotal ? 16 : 13, - fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, - color: isTotal ? const Color(0xFF6366F1) : Colors.black87, - ), - ), - ), - ], - ), - ); - } - - void _handlePayment(AcademicFeeModel fee) { - HapticFeedback.mediumImpact(); - - showDialog( - context: context, - builder: - (context) => AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - title: const Text('Confirm Payment'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Are you sure you want to pay for ${fee.purpose}?'), - const SizedBox(height: 8), - Text( - 'Amount: ${feesController.formatCurrency(fee.totalAmount)}', - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - style: TextButton.styleFrom( - foregroundColor: const Color(0xFF6366F1), - ), - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () { - Navigator.pop(context); - feesController.payFee(fee); - }, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF3B82F6), // Changed to blue - foregroundColor: Colors.white, - ), - child: const Text('Confirm'), - ), - ], - ), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:intl/intl.dart'; +import '../controllers/academic_fees_controller.dart'; +import '../models/academic_fee_model.dart'; + +class AcademicFeesScreen extends StatefulWidget { + const AcademicFeesScreen({Key? key}) : super(key: key); + + @override + State createState() => _AcademicFeesScreenState(); +} + +class _AcademicFeesScreenState extends State + with TickerProviderStateMixin { + late AnimationController _headerAnimationController; + late AnimationController _pulseController; + late Animation _headerScaleAnimation; + late Animation _headerSlideAnimation; + late Animation _pulseAnimation; + + final AcademicFeesController feesController = Get.put( + AcademicFeesController(), + ); + final TextEditingController searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + _setupAnimations(); + } + + void _setupAnimations() { + _headerAnimationController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + + _pulseController = AnimationController( + duration: const Duration(milliseconds: 2000), + vsync: this, + )..repeat(reverse: true); + + _headerScaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation( + parent: _headerAnimationController, + curve: Curves.elasticOut, + ), + ); + + _headerSlideAnimation = Tween( + begin: const Offset(0, -0.3), + end: Offset.zero, + ).animate( + CurvedAnimation( + parent: _headerAnimationController, + curve: Curves.easeOutBack, + ), + ); + + _pulseAnimation = Tween(begin: 1.0, end: 1.05).animate( + CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), + ); + + _headerAnimationController.forward(); + } + + @override + void dispose() { + _headerAnimationController.dispose(); + _pulseController.dispose(); + searchController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey[50], + body: CustomScrollView( + slivers: [ + _buildAnimatedAppBar(), + _buildStatisticsSection(), + _buildFiltersSection(), + _buildFeesListSection(), + ], + ), + ); + } + + Widget _buildAnimatedAppBar() { + return SliverAppBar( + expandedHeight: 120, + floating: false, + pinned: true, + backgroundColor: const Color(0xFF6366F1), + flexibleSpace: FlexibleSpaceBar( + title: AnimatedBuilder( + animation: _headerAnimationController, + builder: (context, child) { + return SlideTransition( + position: _headerSlideAnimation, + child: ScaleTransition( + scale: _headerScaleAnimation, + child: const Text( + 'Academic Fees', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), + ), + ), + ); + }, + ), + background: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)], + ), + ), + child: Stack( + children: [ + Positioned( + top: 20, + right: -20, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + shape: BoxShape.circle, + ), + ), + ), + Positioned( + bottom: -30, + left: -30, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.05), + shape: BoxShape.circle, + ), + ), + ), + ], + ), + ), + ), + actions: [ + Obx( + () => + feesController.overdueCount.value > 0 + ? AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _pulseAnimation.value, + child: IconButton( + icon: Stack( + children: [ + const Icon(Icons.warning_amber_rounded), + Positioned( + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.all(2), + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + constraints: const BoxConstraints( + minWidth: 16, + minHeight: 16, + ), + child: Text( + '${feesController.overdueCount.value}', + style: const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + onPressed: () { + feesController.setFilter('overdue'); + HapticFeedback.lightImpact(); + }, + ), + ); + }, + ) + : const SizedBox(), + ), + IconButton( + icon: const Icon(Icons.refresh), + onPressed: () { + feesController.refreshFees(); + HapticFeedback.lightImpact(); + }, + ), + ], + ); + } + + Widget _buildStatisticsSection() { + return SliverToBoxAdapter( + child: Obx(() { + if (feesController.isLoading.value) { + return _buildStatisticsShimmer(); + } + + return Padding( + padding: const EdgeInsets.all(16), + child: AnimationLimiter( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: AnimationConfiguration.toStaggeredList( + duration: const Duration(milliseconds: 600), + childAnimationBuilder: + (widget) => SlideAnimation( + horizontalOffset: 50.0, + child: FadeInAnimation(child: widget), + ), + children: [ + Expanded( + child: _buildStatCard( + 'Total Pending', + feesController.formatCurrency( + feesController.totalPending.value, + ), + Icons.pending_actions, + const Color(0xFF3B82F6), // Changed to blue + feesController.pendingCount.value + + feesController.overdueCount.value, + ), + ), + const SizedBox(width: 12), + Expanded( + child: _buildStatCard( + 'Total Paid', + feesController.formatCurrency( + feesController.totalPaid.value, + ), + Icons.check_circle, + const Color(0xFF1E40AF), // Changed to darker blue + feesController.paidCount.value, + ), + ), + ], + ), + ), + ), + ); + }), + ); + } + + Widget _buildStatCard( + String title, + String amount, + IconData icon, + Color color, + int count, + ) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: color, size: 20), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + '$count', + style: TextStyle( + color: color, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + Text(title, style: TextStyle(color: Colors.grey[600], fontSize: 14)), + const SizedBox(height: 4), + Text( + amount, + style: TextStyle( + color: color, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } + + Widget _buildStatisticsShimmer() { + return Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded(child: _buildShimmerCard()), + const SizedBox(width: 12), + Expanded(child: _buildShimmerCard()), + ], + ), + ); + } + + Widget _buildShimmerCard() { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 100, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + ), + ); + } + + Widget _buildFiltersSection() { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), // Removed top padding + child: Column( + children: [ + // Search Bar + Container( + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: TextField( + controller: searchController, + decoration: InputDecoration( + hintText: 'Search fees...', + prefixIcon: const Icon( + Icons.search, + color: Color(0xFF6366F1), + ), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + suffixIcon: Obx( + () => + searchController.text.isNotEmpty + ? IconButton( + icon: const Icon( + Icons.clear, + color: Color(0xFF6366F1), + ), + onPressed: () { + searchController.clear(); + feesController.setSearchQuery(''); + }, + ) + : const SizedBox(), + ), + ), + onChanged: (value) { + feesController.setSearchQuery(value); + }, + ), + ), + // Filter Chips + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Obx( + () => Row( + children: [ + _buildFilterChip('All', 'all'), + const SizedBox(width: 8), + _buildFilterChip('Pending', 'pending'), + const SizedBox(width: 8), + _buildFilterChip('Paid', 'paid'), + const SizedBox(width: 8), + _buildFilterChip('Overdue', 'overdue'), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildFilterChip(String label, String value) { + final isSelected = feesController.selectedFilter.value == value; + return FilterChip( + label: Text(label), + selected: isSelected, + onSelected: (selected) { + feesController.setFilter(value); + HapticFeedback.selectionClick(); + }, + backgroundColor: Colors.white, + selectedColor: const Color(0xFF6366F1).withOpacity(0.1), + checkmarkColor: const Color(0xFF6366F1), + labelStyle: TextStyle( + color: isSelected ? const Color(0xFF6366F1) : Colors.grey[600], + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + side: BorderSide( + color: isSelected ? const Color(0xFF6366F1) : Colors.grey[300]!, + ), + ); + } + + Widget _buildFeesListSection() { + return Obx(() { + if (feesController.isLoading.value) { + return _buildLoadingList(); + } + + if (feesController.errorMessage.value.isNotEmpty) { + return _buildErrorState(); + } + + final filteredFees = feesController.filteredFees; + + if (filteredFees.isEmpty) { + return _buildEmptyState(); + } + + return SliverPadding( + padding: const EdgeInsets.all(16), + sliver: AnimationLimiter( + child: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 600), + child: SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: _buildFeeCard(filteredFees[index], index), + ), + ), + ); + }, childCount: filteredFees.length), + ), + ), + ); + }); + } + + Widget _buildFeeCard(AcademicFeeModel fee, int index) { + final statusColor = feesController.getStatusColor(fee.currentStatus); + final typeIcon = feesController.getTypeIcon(fee.type); + + return Container( + margin: const EdgeInsets.only(bottom: 16), + child: Material( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + elevation: fee.currentStatus == FeeStatus.overdue ? 8 : 4, + shadowColor: statusColor.withOpacity(0.3), + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () => _showFeeDetails(fee), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: + fee.currentStatus == FeeStatus.overdue + ? Colors.red.withOpacity(0.3) + : Colors.transparent, + width: 2, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(typeIcon, color: statusColor, size: 24), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + fee.purpose, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 4), + Text( + feesController.getTypeDisplayName(fee.type), + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: statusColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + fee.currentStatus + .toString() + .split('.') + .last + .toUpperCase(), + style: TextStyle( + color: statusColor, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + if (fee.description != null) ...[ + Text( + fee.description!, + style: TextStyle(fontSize: 14, color: Colors.grey[600]), + ), + const SizedBox(height: 12), + ], + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Amount', + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + Text( + feesController.formatCurrency(fee.totalAmount), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: statusColor, + ), + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Due Date', + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + Text( + DateFormat('MMM dd, yyyy').format(fee.deadline), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + if (fee.currentStatus != FeeStatus.paid) ...[ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + fee.isOverdue ? 'Overdue' : 'Days Left', + style: TextStyle( + fontSize: 12, + color: Colors.grey[500], + ), + ), + Text( + fee.isOverdue + ? '${DateTime.now().difference(fee.deadline).inDays}d' + : '${fee.daysRemaining}d', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: fee.isOverdue ? Colors.red : statusColor, + ), + ), + ], + ), + ], + ], + ), + if (fee.currentStatus != FeeStatus.paid) ...[ + const SizedBox(height: 16), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => _handlePayment(fee), + style: ElevatedButton.styleFrom( + backgroundColor: const Color( + 0xFF3B82F6, + ), // Changed to blue + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 2, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.payment, size: 20), + const SizedBox(width: 8), + Text( + fee.isOverdue ? 'Pay Now (Overdue)' : 'Pay Now', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ], + ), + ), + ), + ), + ); + } + + Widget _buildLoadingList() { + return SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 180, + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + ), + ), + ); + }, childCount: 5), + ), + ); + } + + Widget _buildErrorState() { + return SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + 'Failed to load fees', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 8), + Text( + feesController.errorMessage.value, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[500]), + ), + const SizedBox(height: 24), + ElevatedButton.icon( + onPressed: () => feesController.refreshFees(), + icon: const Icon(Icons.refresh), + label: const Text('Retry'), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6366F1), + foregroundColor: Colors.white, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildEmptyState() { + return SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + Icon( + Icons.receipt_long_outlined, + size: 64, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + 'No fees found', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 8), + Text( + 'No academic fees match your current filter', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[500]), + ), + ], + ), + ), + ), + ); + } + + void _showFeeDetails(AcademicFeeModel fee) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => _buildFeeDetailsSheet(fee), + ); + } + + Widget _buildFeeDetailsSheet(AcademicFeeModel fee) { + return Container( + height: MediaQuery.of(context).size.height * 0.85, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + child: Column( + children: [ + // Handle + Container( + margin: const EdgeInsets.only(top: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + // Content + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: feesController + .getStatusColor(fee.currentStatus) + .withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + feesController.getTypeIcon(fee.type), + color: feesController.getStatusColor( + fee.currentStatus, + ), + size: 28, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + fee.purpose, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + feesController.getTypeDisplayName(fee.type), + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 20), + // Fee breakdown section + if (fee.items.isNotEmpty) ...[ + const Text( + 'Fee Breakdown', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Column( + children: [ + ...fee.items.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + border: + index < fee.items.length - 1 + ? Border( + bottom: BorderSide( + color: Colors.grey[200]!, + width: 1, + ), + ) + : null, + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + item.name, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + if (item.description != null) ...[ + const SizedBox(height: 2), + Text( + item.name, + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ], + ), + ), + const SizedBox(width: 8), + Text( + feesController.formatCurrency(item.amount), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: feesController.getStatusColor( + fee.currentStatus, + ), + ), + ), + ], + ), + ); + }).toList(), + // Subtotal + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Subtotal', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text( + feesController.formatCurrency(fee.amount), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + ], + ), + ), + ], + ), + ), + const SizedBox(height: 12), + ] else ...[ + _buildDetailRow( + 'Amount', + feesController.formatCurrency(fee.amount), + ), + ], + if (fee.lateFee != null && fee.isOverdue) + _buildDetailRow( + 'Late Fee', + feesController.formatCurrency(fee.lateFee!), + ), + if (fee.discount != null) + _buildDetailRow( + 'Discount', + '-${feesController.formatCurrency(fee.discount!)}', + ), + const Divider(height: 20), + _buildDetailRow( + 'Total', + feesController.formatCurrency(fee.totalAmount), + isTotal: true, + ), + const SizedBox(height: 12), + _buildDetailRow( + 'Due Date', + DateFormat('MMMM dd, yyyy').format(fee.deadline), + ), + _buildDetailRow('Academic Year', fee.academicYear), + _buildDetailRow('Semester', fee.semester), + if (fee.description != null) + _buildDetailRow('Description', fee.description!), + if (fee.status == FeeStatus.paid) ...[ + const SizedBox(height: 12), + _buildDetailRow( + 'Payment Date', + DateFormat('MMMM dd, yyyy').format(fee.paidAt!), + ), + _buildDetailRow('Receipt Number', fee.receiptNumber!), + ], + const SizedBox(height: 80), // Space for button + ], + ), + ), + ), + // Payment Button (Fixed at bottom) + if (fee.currentStatus != FeeStatus.paid) + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, -2), + ), + ], + ), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop(context); + _handlePayment(fee); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF3B82F6), // Changed to blue + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: Text( + 'Pay ${feesController.formatCurrency(fee.totalAmount)}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildDetailRow(String label, String value, {bool isTotal = false}) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 100, + child: Text( + label, + style: TextStyle( + color: Colors.grey[600], + fontSize: isTotal ? 14 : 13, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: isTotal ? 16 : 13, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + color: isTotal ? const Color(0xFF6366F1) : Colors.black87, + ), + ), + ), + ], + ), + ); + } + + void _handlePayment(AcademicFeeModel fee) { + HapticFeedback.mediumImpact(); + + showDialog( + context: context, + builder: + (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + title: const Text('Confirm Payment'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Are you sure you want to pay for ${fee.purpose}?'), + const SizedBox(height: 8), + Text( + 'Amount: ${feesController.formatCurrency(fee.totalAmount)}', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + foregroundColor: const Color(0xFF6366F1), + ), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () { + Navigator.pop(context); + feesController.payFee(fee); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF3B82F6), // Changed to blue + foregroundColor: Colors.white, + ), + child: const Text('Confirm'), + ), + ], + ), + ); + } +} diff --git a/lib/views/academic_result_detail_screen.dart b/lib/views/academic_result_detail_screen.dart new file mode 100644 index 0000000..f7cc4cb --- /dev/null +++ b/lib/views/academic_result_detail_screen.dart @@ -0,0 +1,339 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controllers/academic_result_controller.dart'; +import '../controllers/auth_controller.dart'; +import '../models/academic_result_model.dart'; +import '../services/pdf_marksheet_service.dart'; + +class AcademicResultDetailScreen extends StatelessWidget { + final AcademicResultModel result; + + const AcademicResultDetailScreen({super.key, required this.result}); + + @override + Widget build(BuildContext context) { + final resultController = Get.find(); + + return Scaffold( + backgroundColor: const Color(0xFFF8FAFC), + appBar: AppBar( + title: Text('${result.semester} Semester'), + backgroundColor: const Color(0xFF667eea), + foregroundColor: Colors.white, + elevation: 0, + actions: [ + IconButton( + tooltip: 'Download PDF', + onPressed: () => _downloadPdf(result), + icon: const Icon(Icons.picture_as_pdf), + ), + ], + ), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + _buildSummaryCard(context, resultController), + const SizedBox(height: 16), + _buildCoursesHeader(), + const SizedBox(height: 12), + ...result.courses.map( + (course) => _buildCourseCard(course, resultController), + ), + ], + ), + bottomNavigationBar: SafeArea( + minimum: const EdgeInsets.all(16), + child: ElevatedButton.icon( + onPressed: () => _downloadPdf(result), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF667eea), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + icon: const Icon(Icons.picture_as_pdf), + label: const Text( + 'Download Marksheet PDF', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), + ); + } + + Widget _buildSummaryCard( + BuildContext context, + AcademicResultController resultController, + ) { + final gpaColor = resultController.getGPAColor(result.totalGPA); + + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF667eea), Color(0xFF764ba2)], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: const Color(0xFF667eea).withValues(alpha: 0.25), + blurRadius: 18, + offset: const Offset(0, 10), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${result.semester} Semester', + style: const TextStyle( + color: Colors.white, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 6), + Text( + 'Academic Year ${result.academicYear}', + style: TextStyle( + color: Colors.white.withValues(alpha: 0.85), + fontSize: 14, + ), + ), + const SizedBox(height: 20), + Row( + children: [ + _buildMetric('GPA', result.totalGPA.toStringAsFixed(2), gpaColor), + _buildMetric( + 'Credits', + result.totalCredits.toStringAsFixed(1), + Colors.white, + ), + _buildMetric( + 'Courses', + result.courses.length.toString(), + Colors.white, + ), + ], + ), + ], + ), + ); + } + + Widget _buildMetric(String label, String value, Color accentColor) { + return Expanded( + child: Container( + margin: const EdgeInsets.only(right: 8), + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.14), + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.white.withValues(alpha: 0.18)), + ), + child: Column( + children: [ + Text( + value, + style: TextStyle( + color: accentColor == Colors.white ? Colors.white : accentColor, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + color: Colors.white.withValues(alpha: 0.78), + fontSize: 12, + ), + ), + ], + ), + ), + ); + } + + Widget _buildCoursesHeader() { + return Row( + children: [ + const Icon(Icons.menu_book, color: Color(0xFF667eea)), + const SizedBox(width: 8), + const Text( + 'Course Results', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const Spacer(), + Text( + '${result.courses.length} courses', + style: TextStyle( + color: Colors.grey[600], + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + Widget _buildCourseCard( + CourseResult course, + AcademicResultController resultController, + ) { + final gradeColor = resultController.getGradeColor(course.grade); + + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.grey[200]!), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.04), + blurRadius: 10, + offset: const Offset(0, 5), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + course.courseName, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w700, + color: Colors.black87, + ), + ), + const SizedBox(height: 6), + Text( + '${course.courseId} โ€ข ${course.credit} credits', + style: TextStyle(fontSize: 13, color: Colors.grey[600]), + ), + ], + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + decoration: BoxDecoration( + color: gradeColor.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + border: Border.all(color: gradeColor.withValues(alpha: 0.35)), + ), + child: Text( + course.grade, + style: TextStyle( + color: gradeColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 6), + Text( + 'GPA ${course.gpa}', + style: TextStyle( + color: resultController.getGPAColor( + double.tryParse(course.gpa) ?? 0, + ), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + ); + } + + Future _downloadPdf(AcademicResultModel result) async { + final authController = Get.find(); + final user = authController.user; + + if (user == null) { + Get.snackbar( + 'Error', + 'User information not available. Please log in again.', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + try { + Get.dialog( + PopScope( + canPop: false, + child: Dialog( + backgroundColor: Colors.transparent, + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Color(0xFF667eea), + ), + ), + const SizedBox(height: 16), + Text( + 'Generating ${result.semester} marksheet...', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + barrierDismissible: false, + ); + + await PdfMarksheetService.generateSemesterMarksheet(result, user); + Get.back(); + Get.snackbar( + 'Success', + 'Marksheet PDF generated and opened successfully.', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } catch (e) { + Get.back(); + Get.snackbar( + 'Error', + 'Failed to generate marksheet: $e', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + } +} diff --git a/lib/views/academic_result_screen.dart b/lib/views/academic_result_screen.dart index 052a2a9..901b7ad 100644 --- a/lib/views/academic_result_screen.dart +++ b/lib/views/academic_result_screen.dart @@ -1,15 +1,14 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:shimmer/shimmer.dart'; import '../controllers/academic_result_controller.dart'; import '../controllers/auth_controller.dart'; import '../models/academic_result_model.dart'; -import '../services/pdf_marksheet_service.dart'; +import 'academic_result_detail_screen.dart'; class AcademicResultScreen extends StatefulWidget { - const AcademicResultScreen({Key? key}) : super(key: key); + const AcademicResultScreen({super.key}); @override State createState() => _AcademicResultScreenState(); @@ -27,8 +26,6 @@ class _AcademicResultScreenState extends State AcademicResultController(), ); - final AuthController authController = Get.find(); - @override void initState() { super.initState(); @@ -77,282 +74,10 @@ class _AcademicResultScreenState extends State super.dispose(); } - // PDF Generation Methods - Future _generateComprehensiveMarksheet() async { - try { - if (authController.user == null) { - Get.snackbar( - 'Error', - 'User information not available. Please log in again.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 3), - ); - return; - } - - if (resultController.academicResults.isEmpty) { - Get.snackbar( - 'Error', - 'No academic results available to generate marksheet.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 3), - ); - return; - } - - // Show loading dialog - Get.dialog( - WillPopScope( - onWillPop: () async => false, - child: Dialog( - backgroundColor: Colors.transparent, - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation( - Color(0xFF667eea), - ), - ), - const SizedBox(height: 16), - const Text( - 'Generating Comprehensive Marksheet...', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - 'Processing ${resultController.academicResults.length} semesters', - style: TextStyle(fontSize: 14, color: Colors.grey[600]), - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ), - barrierDismissible: false, - ); - - await PdfMarksheetService.generateComprehensiveMarksheet( - resultController.academicResults, - authController.user!, - ); - - Get.back(); // Close loading dialog - - Get.snackbar( - 'Success! ๐ŸŽ‰', - 'Comprehensive marksheet generated successfully!\nCheck your downloads folder.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - duration: const Duration(seconds: 4), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } catch (e) { - Get.back(); // Close loading dialog - Get.snackbar( - 'Error', - 'Failed to generate marksheet: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 4), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } - } - - Future _generateSemesterMarksheet(AcademicResultModel result) async { - try { - if (authController.user == null) { - Get.snackbar( - 'Error', - 'User information not available. Please log in again.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 3), - ); - return; - } - - // Show loading dialog - Get.dialog( - WillPopScope( - onWillPop: () async => false, - child: Dialog( - backgroundColor: Colors.transparent, - child: Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation( - Color(0xFF667eea), - ), - ), - const SizedBox(height: 16), - const Text( - 'Generating Semester Marksheet...', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - '${result.semester} Semester - ${result.academicYear}', - style: TextStyle(fontSize: 14, color: Colors.grey[600]), - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ), - barrierDismissible: false, - ); - - await PdfMarksheetService.generateSemesterMarksheet( - result, - authController.user!, - ); - - Get.back(); // Close loading dialog - - Get.snackbar( - 'Success! ๐Ÿ“„', - 'Semester marksheet generated successfully!\nCheck your downloads folder.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - duration: const Duration(seconds: 4), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } catch (e) { - Get.back(); // Close loading dialog - Get.snackbar( - 'Error', - 'Failed to generate marksheet: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 4), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } - } - - void _showPdfOptions() { - showModalBottomSheet( - context: context, - backgroundColor: Colors.transparent, - builder: - (context) => Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - margin: const EdgeInsets.only(top: 12), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(height: 20), - const Text( - 'Generate Marksheet', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 20), - ListTile( - leading: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon(Icons.description, color: Colors.blue), - ), - title: const Text('Comprehensive Marksheet'), - subtitle: const Text('All semesters with detailed analysis'), - onTap: () { - Navigator.pop(context); - _generateComprehensiveMarksheet(); - }, - ), - ListTile( - leading: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.green.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon(Icons.school, color: Colors.green), - ), - title: const Text('Current Semester'), - subtitle: const Text('Selected semester only'), - onTap: () { - Navigator.pop(context); - if (resultController.filteredResults.isNotEmpty) { - _generateSemesterMarksheet( - resultController.filteredResults.first, - ); - } - }, - ), - const SizedBox(height: 20), - ], - ), - ), - ); - } - @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[50], - floatingActionButton: Obx(() { - if (resultController.academicResults.isEmpty) return const SizedBox(); - - return FloatingActionButton.extended( - onPressed: _showPdfOptions, - backgroundColor: const Color(0xFF667eea), - icon: const Icon(Icons.picture_as_pdf, color: Colors.white), - label: const Text( - 'Generate PDF', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), - ), - ); - }), body: CustomScrollView( slivers: [ _buildAnimatedAppBar(), @@ -394,7 +119,7 @@ class _AcademicResultScreenState extends State icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: const Icon( @@ -436,7 +161,7 @@ class _AcademicResultScreenState extends State child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(16), ), child: const Icon( @@ -526,28 +251,25 @@ class _AcademicResultScreenState extends State ], ), const SizedBox(height: 16), - if (resultController.academicResults.isNotEmpty) + if (resultController.isUsingSampleResults.value) Container( width: double.infinity, - child: ElevatedButton.icon( - onPressed: _generateComprehensiveMarksheet, - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF667eea), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - elevation: 2, + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.orange.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.orange.withValues(alpha: 0.35), ), - icon: const Icon(Icons.picture_as_pdf), - label: const Text( - 'Generate Comprehensive Marksheet', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + ), + child: const Text( + 'Sample results are shown for this profile until official results are added.', + style: TextStyle( + color: Colors.deepOrange, + fontWeight: FontWeight.w600, ), + textAlign: TextAlign.center, ), ), ], @@ -577,7 +299,7 @@ class _AcademicResultScreenState extends State borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 5), ), @@ -591,7 +313,7 @@ class _AcademicResultScreenState extends State Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: color.withOpacity(0.1), + color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 20), @@ -777,46 +499,45 @@ class _AcademicResultScreenState extends State } Widget _buildResultCard(AcademicResultModel result) { + final gpaColor = resultController.getGPAColor(result.totalGPA); + return Container( - margin: const EdgeInsets.only(bottom: 16), + margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.grey[200]!), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.04), blurRadius: 10, offset: const Offset(0, 5), ), ], ), - child: Column( - children: [ - // Header - Container( + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(14), + onTap: () => Get.to(() => AcademicResultDetailScreen(result: result)), + child: Padding( padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue.shade50, Colors.purple.shade50], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), child: Row( children: [ Container( - padding: const EdgeInsets.all(8), + width: 48, + height: 48, decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), + color: const Color(0xFF667eea).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: const Icon( + Icons.school, + color: Color(0xFF667eea), + size: 24, ), - child: const Icon(Icons.school, color: Colors.blue, size: 20), ), - const SizedBox(width: 12), + const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -825,13 +546,14 @@ class _AcademicResultScreenState extends State '${result.semester} Semester', style: const TextStyle( fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w700, color: Colors.black87, ), ), + const SizedBox(height: 4), Text( - 'Academic Year: ${result.academicYear}', - style: TextStyle(fontSize: 14, color: Colors.grey[600]), + result.academicYear, + style: TextStyle(fontSize: 13, color: Colors.grey[600]), ), ], ), @@ -839,145 +561,28 @@ class _AcademicResultScreenState extends State Container( padding: const EdgeInsets.symmetric( horizontal: 12, - vertical: 6, + vertical: 7, ), decoration: BoxDecoration( - color: resultController - .getGPAColor(result.totalGPA) - .withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: resultController.getGPAColor(result.totalGPA), - width: 1, - ), + color: gpaColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(999), + border: Border.all(color: gpaColor.withValues(alpha: 0.3)), ), child: Text( - 'GPA: ${result.totalGPA.toStringAsFixed(2)}', + 'CGPA ${result.totalGPA.toStringAsFixed(2)}', style: TextStyle( - fontSize: 14, + fontSize: 13, fontWeight: FontWeight.bold, - color: resultController.getGPAColor(result.totalGPA), - ), - ), - ), - IconButton( - onPressed: () => _generateSemesterMarksheet(result), - icon: Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: Colors.blue.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.picture_as_pdf, - color: Colors.blue, - size: 18, + color: gpaColor, ), ), ), + const SizedBox(width: 6), + Icon(Icons.chevron_right, color: Colors.grey[500]), ], ), ), - // Courses - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: result.courses.length, - itemBuilder: (context, index) { - final course = result.courses[index]; - return _buildCourseTile( - course, - index == result.courses.length - 1, - ); - }, - ), - ], - ), - ); - } - - Widget _buildCourseTile(CourseResult course, bool isLast) { - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - border: - isLast - ? null - : Border( - bottom: BorderSide(color: Colors.grey[200]!, width: 1), - ), - ), - child: Row( - children: [ - Expanded( - flex: 3, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - course.courseName, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), - ), - const SizedBox(height: 4), - Text( - 'Course ID: ${course.courseId}', - style: TextStyle(fontSize: 12, color: Colors.grey[600]), - ), - ], - ), - ), - const SizedBox(width: 12), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: resultController - .getGradeColor(course.grade) - .withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: resultController.getGradeColor(course.grade), - width: 1, - ), - ), - child: Text( - course.grade, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: resultController.getGradeColor(course.grade), - ), - ), - ), - const SizedBox(width: 12), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text( - '${course.credit} Credits', - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 2), - Text( - 'GPA: ${course.gpa}', - style: TextStyle( - fontSize: 12, - color: resultController.getGPAColor( - double.tryParse(course.gpa) ?? 0.0, - ), - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ], + ), ), ); } @@ -1007,65 +612,66 @@ class _AcademicResultScreenState extends State } Widget _buildErrorState() { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Icon(Icons.error_outline, size: 64, color: Colors.red[400]), - const SizedBox(height: 16), - Text( - 'Error Loading Results', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Text( - resultController.errorMessage.value, - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey[500]), - ), - const SizedBox(height: 16), - ElevatedButton( - onPressed: () => resultController.refreshResults(), - child: const Text('Retry'), + return Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + Icon(Icons.error_outline, size: 64, color: Colors.red[400]), + const SizedBox(height: 16), + Text( + 'Error Loading Results', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey[600], ), - ], - ), + ), + const SizedBox(height: 8), + Text( + resultController.errorMessage.value, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[500]), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () => resultController.refreshResults(), + child: const Text('Retry'), + ), + ], ), ), ); } Widget _buildEmptyState() { - return SliverToBoxAdapter( - child: Center( - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - children: [ - Icon(Icons.bar_chart_outlined, size: 64, color: Colors.grey[400]), - const SizedBox(height: 16), - Text( - 'No Results Found', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Text( - 'No academic results match your current filter', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.grey[500]), + final authController = Get.find(); + final studentId = authController.user?.id.trim(); + + return Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + children: [ + Icon(Icons.bar_chart_outlined, size: 64, color: Colors.grey[400]), + const SizedBox(height: 16), + Text( + 'No Results Found', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey[600], ), - ], - ), + ), + const SizedBox(height: 8), + Text( + studentId == null || studentId.isEmpty + ? 'No academic results are available for this account.' + : 'No academic results found for student ID $studentId.', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey[500]), + ), + ], ), ), ); diff --git a/lib/views/all_notifications_screen.dart b/lib/views/all_notifications_screen.dart index 23aa349..fd0e4e3 100644 --- a/lib/views/all_notifications_screen.dart +++ b/lib/views/all_notifications_screen.dart @@ -1,566 +1,566 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:timeago/timeago.dart' as timeago; -import '../controllers/notifications_controller.dart'; -import '../models/notification_model.dart'; - -class AllNotificationsScreen extends StatelessWidget { - const AllNotificationsScreen({super.key}); - - @override - Widget build(BuildContext context) { - final notificationsController = Get.put(NotificationsController()); - - return Scaffold( - appBar: AppBar( - title: const Text( - 'Notifications', - style: TextStyle(fontWeight: FontWeight.w600, color: Colors.white), - ), - backgroundColor: const Color(0xFF6366F1), - elevation: 0, - iconTheme: const IconThemeData(color: Colors.white), - actions: [ - Obx( - () => - notificationsController.unreadCount.value > 0 - ? IconButton( - onPressed: () => notificationsController.markAllAsRead(), - icon: const Icon(Icons.done_all), - tooltip: 'Mark all as read', - ) - : const SizedBox.shrink(), - ), - IconButton( - onPressed: () => notificationsController.refreshNotifications(), - icon: const Icon(Icons.refresh), - tooltip: 'Refresh', - ), - ], - ), - body: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Color(0xFF6366F1), Color(0xFFF8FAFC)], - stops: [0.0, 0.3], - ), - ), - child: Obx(() { - if (notificationsController.isLoading.value) { - return const Center( - child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Color(0xFF6366F1)), - ), - ); - } - - if (notificationsController.notifications.isEmpty) { - return _buildEmptyState(); - } - - return RefreshIndicator( - onRefresh: () async { - notificationsController.refreshNotifications(); - }, - child: Column( - children: [ - // Stats header - _buildStatsHeader(notificationsController), - // Notifications list - Expanded( - child: ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: notificationsController.notifications.length, - itemBuilder: (context, index) { - final notification = - notificationsController.notifications[index]; - return _buildNotificationTile( - context, - notification, - notificationsController, - ); - }, - ), - ), - ], - ), - ); - }), - ), - ); - } - - Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(32), - decoration: BoxDecoration( - color: Colors.grey.shade100, - shape: BoxShape.circle, - ), - child: Icon( - Icons.notifications_off_outlined, - size: 64, - color: Colors.grey.shade400, - ), - ), - const SizedBox(height: 24), - Text( - 'No Notifications', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w600, - color: Colors.grey.shade600, - ), - ), - const SizedBox(height: 8), - Text( - 'You\'ll see notifications here when you receive them', - style: TextStyle(fontSize: 16, color: Colors.grey.shade500), - textAlign: TextAlign.center, - ), - ], - ), - ); - } - - Widget _buildStatsHeader(NotificationsController controller) { - return Container( - margin: const EdgeInsets.all(16), - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: Row( - children: [ - Expanded( - child: _buildStatItem( - 'Total', - controller.notifications.length.toString(), - Icons.notifications, - const Color(0xFF6366F1), - ), - ), - Container(width: 1, height: 40, color: Colors.grey.shade200), - Expanded( - child: _buildStatItem( - 'Unread', - controller.unreadCount.value.toString(), - Icons.mark_email_unread, - Colors.orange, - ), - ), - Container(width: 1, height: 40, color: Colors.grey.shade200), - Expanded( - child: _buildStatItem( - 'Read', - controller.readNotifications.length.toString(), - Icons.mark_email_read, - Colors.green, - ), - ), - ], - ), - ); - } - - Widget _buildStatItem( - String label, - String value, - IconData icon, - Color color, - ) { - return Column( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, color: color, size: 20), - ), - const SizedBox(height: 8), - Text( - value, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - Text( - label, - style: TextStyle(fontSize: 12, color: Colors.grey.shade600), - ), - ], - ); - } - - Widget _buildNotificationTile( - BuildContext context, - NotificationModel notification, - NotificationsController controller, - ) { - return Container( - margin: const EdgeInsets.only(bottom: 12), - decoration: BoxDecoration( - color: notification.isRead ? Colors.white : Colors.blue.shade50, - borderRadius: BorderRadius.circular(12), - border: - notification.isRead - ? Border.all(color: Colors.grey.shade200) - : Border.all(color: Colors.blue.shade200), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { - if (!notification.isRead) { - controller.markAsRead(notification.id); - } - _showNotificationDetails(context, notification); - }, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Notification icon/avatar - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: _getNotificationTypeColor( - notification.type, - ).withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - _getNotificationTypeIcon(notification.type), - color: _getNotificationTypeColor(notification.type), - size: 24, - ), - ), - const SizedBox(width: 12), - // Content - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - notification.title, - style: TextStyle( - fontSize: 16, - fontWeight: - notification.isRead - ? FontWeight.w500 - : FontWeight.w600, - color: Colors.grey.shade800, - ), - ), - ), - if (!notification.isRead) - Container( - width: 8, - height: 8, - decoration: const BoxDecoration( - color: Colors.blue, - shape: BoxShape.circle, - ), - ), - ], - ), - const SizedBox(height: 4), - Text( - notification.message, - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade600, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.access_time, - size: 14, - color: Colors.grey.shade500, - ), - const SizedBox(width: 4), - Text( - timeago.format(notification.createdAt), - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade500, - ), - ), - const Spacer(), - _buildNotificationTypeChip(notification.type), - ], - ), - ], - ), - ), - // Actions - PopupMenuButton( - onSelected: (value) { - switch (value) { - case 'mark_read': - controller.markAsRead(notification.id); - break; - case 'delete': - _showDeleteConfirmation( - context, - notification, - controller, - ); - break; - } - }, - itemBuilder: - (context) => [ - if (!notification.isRead) - const PopupMenuItem( - value: 'mark_read', - child: Row( - children: [ - Icon(Icons.mark_email_read, size: 18), - SizedBox(width: 8), - Text('Mark as read'), - ], - ), - ), - const PopupMenuItem( - value: 'delete', - child: Row( - children: [ - Icon(Icons.delete, size: 18), - SizedBox(width: 8), - Text('Delete'), - ], - ), - ), - ], - child: Icon( - Icons.more_vert, - color: Colors.grey.shade400, - size: 20, - ), - ), - ], - ), - ), - ), - ), - ); - } - - Widget _buildNotificationTypeChip(String type) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: _getNotificationTypeColor(type).withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - type.toUpperCase(), - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w600, - color: _getNotificationTypeColor(type), - ), - ), - ); - } - - IconData _getNotificationTypeIcon(String type) { - switch (type.toLowerCase()) { - case 'academic': - return Icons.school; - case 'fee': - return Icons.payment; - case 'exam': - return Icons.quiz; - case 'event': - return Icons.event; - case 'announcement': - return Icons.campaign; - case 'system': - return Icons.settings; - default: - return Icons.notifications; - } - } - - Color _getNotificationTypeColor(String type) { - switch (type.toLowerCase()) { - case 'academic': - return Colors.blue; - case 'fee': - return Colors.green; - case 'exam': - return Colors.orange; - case 'event': - return Colors.purple; - case 'announcement': - return Colors.red; - case 'system': - return Colors.grey; - default: - return const Color(0xFF6366F1); - } - } - - void _showNotificationDetails( - BuildContext context, - NotificationModel notification, - ) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: - (context) => Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.vertical(top: Radius.circular(20)), - ), - child: DraggableScrollableSheet( - initialChildSize: 0.6, - maxChildSize: 0.9, - minChildSize: 0.3, - builder: (context, scrollController) { - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Handle - Center( - child: Container( - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(2), - ), - ), - ), - const SizedBox(height: 20), - // Header - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: _getNotificationTypeColor( - notification.type, - ).withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - _getNotificationTypeIcon(notification.type), - color: _getNotificationTypeColor( - notification.type, - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - notification.title, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - Text( - timeago.format(notification.createdAt), - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade600, - ), - ), - ], - ), - ), - _buildNotificationTypeChip(notification.type), - ], - ), - const SizedBox(height: 20), - // Content - Expanded( - child: SingleChildScrollView( - controller: scrollController, - child: Text( - notification.message, - style: const TextStyle(fontSize: 16, height: 1.6), - ), - ), - ), - ], - ), - ); - }, - ), - ), - ); - } - - void _showDeleteConfirmation( - BuildContext context, - NotificationModel notification, - NotificationsController controller, - ) { - showDialog( - context: context, - builder: - (context) => AlertDialog( - title: const Text('Delete Notification'), - content: const Text( - 'Are you sure you want to delete this notification?', - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - controller.deleteNotification(notification.id); - }, - child: const Text('Delete'), - ), - ], - ), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:timeago/timeago.dart' as timeago; +import '../controllers/notifications_controller.dart'; +import '../models/notification_model.dart'; + +class AllNotificationsScreen extends StatelessWidget { + const AllNotificationsScreen({super.key}); + + @override + Widget build(BuildContext context) { + final notificationsController = Get.put(NotificationsController()); + + return Scaffold( + appBar: AppBar( + title: const Text( + 'Notifications', + style: TextStyle(fontWeight: FontWeight.w600, color: Colors.white), + ), + backgroundColor: const Color(0xFF6366F1), + elevation: 0, + iconTheme: const IconThemeData(color: Colors.white), + actions: [ + Obx( + () => + notificationsController.unreadCount.value > 0 + ? IconButton( + onPressed: () => notificationsController.markAllAsRead(), + icon: const Icon(Icons.done_all), + tooltip: 'Mark all as read', + ) + : const SizedBox.shrink(), + ), + IconButton( + onPressed: () => notificationsController.refreshNotifications(), + icon: const Icon(Icons.refresh), + tooltip: 'Refresh', + ), + ], + ), + body: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Color(0xFF6366F1), Color(0xFFF8FAFC)], + stops: [0.0, 0.3], + ), + ), + child: Obx(() { + if (notificationsController.isLoading.value) { + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Color(0xFF6366F1)), + ), + ); + } + + if (notificationsController.notifications.isEmpty) { + return _buildEmptyState(); + } + + return RefreshIndicator( + onRefresh: () async { + notificationsController.refreshNotifications(); + }, + child: Column( + children: [ + // Stats header + _buildStatsHeader(notificationsController), + // Notifications list + Expanded( + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: notificationsController.notifications.length, + itemBuilder: (context, index) { + final notification = + notificationsController.notifications[index]; + return _buildNotificationTile( + context, + notification, + notificationsController, + ); + }, + ), + ), + ], + ), + ); + }), + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + color: Colors.grey.shade100, + shape: BoxShape.circle, + ), + child: Icon( + Icons.notifications_off_outlined, + size: 64, + color: Colors.grey.shade400, + ), + ), + const SizedBox(height: 24), + Text( + 'No Notifications', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w600, + color: Colors.grey.shade600, + ), + ), + const SizedBox(height: 8), + Text( + 'You\'ll see notifications here when you receive them', + style: TextStyle(fontSize: 16, color: Colors.grey.shade500), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + Widget _buildStatsHeader(NotificationsController controller) { + return Container( + margin: const EdgeInsets.all(16), + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: _buildStatItem( + 'Total', + controller.notifications.length.toString(), + Icons.notifications, + const Color(0xFF6366F1), + ), + ), + Container(width: 1, height: 40, color: Colors.grey.shade200), + Expanded( + child: _buildStatItem( + 'Unread', + controller.unreadCount.value.toString(), + Icons.mark_email_unread, + Colors.orange, + ), + ), + Container(width: 1, height: 40, color: Colors.grey.shade200), + Expanded( + child: _buildStatItem( + 'Read', + controller.readNotifications.length.toString(), + Icons.mark_email_read, + Colors.green, + ), + ), + ], + ), + ); + } + + Widget _buildStatItem( + String label, + String value, + IconData icon, + Color color, + ) { + return Column( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon(icon, color: color, size: 20), + ), + const SizedBox(height: 8), + Text( + value, + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + Text( + label, + style: TextStyle(fontSize: 12, color: Colors.grey.shade600), + ), + ], + ); + } + + Widget _buildNotificationTile( + BuildContext context, + NotificationModel notification, + NotificationsController controller, + ) { + return Container( + margin: const EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: notification.isRead ? Colors.white : Colors.blue.shade50, + borderRadius: BorderRadius.circular(12), + border: + notification.isRead + ? Border.all(color: Colors.grey.shade200) + : Border.all(color: Colors.blue.shade200), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + if (!notification.isRead) { + controller.markAsRead(notification.id); + } + _showNotificationDetails(context, notification); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Notification icon/avatar + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: _getNotificationTypeColor( + notification.type, + ).withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + _getNotificationTypeIcon(notification.type), + color: _getNotificationTypeColor(notification.type), + size: 24, + ), + ), + const SizedBox(width: 12), + // Content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + notification.title, + style: TextStyle( + fontSize: 16, + fontWeight: + notification.isRead + ? FontWeight.w500 + : FontWeight.w600, + color: Colors.grey.shade800, + ), + ), + ), + if (!notification.isRead) + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Colors.blue, + shape: BoxShape.circle, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + notification.message, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.access_time, + size: 14, + color: Colors.grey.shade500, + ), + const SizedBox(width: 4), + Text( + timeago.format(notification.createdAt), + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade500, + ), + ), + const Spacer(), + _buildNotificationTypeChip(notification.type), + ], + ), + ], + ), + ), + // Actions + PopupMenuButton( + onSelected: (value) { + switch (value) { + case 'mark_read': + controller.markAsRead(notification.id); + break; + case 'delete': + _showDeleteConfirmation( + context, + notification, + controller, + ); + break; + } + }, + itemBuilder: + (context) => [ + if (!notification.isRead) + const PopupMenuItem( + value: 'mark_read', + child: Row( + children: [ + Icon(Icons.mark_email_read, size: 18), + SizedBox(width: 8), + Text('Mark as read'), + ], + ), + ), + const PopupMenuItem( + value: 'delete', + child: Row( + children: [ + Icon(Icons.delete, size: 18), + SizedBox(width: 8), + Text('Delete'), + ], + ), + ), + ], + child: Icon( + Icons.more_vert, + color: Colors.grey.shade400, + size: 20, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildNotificationTypeChip(String type) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: _getNotificationTypeColor(type).withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + type.toUpperCase(), + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: _getNotificationTypeColor(type), + ), + ), + ); + } + + IconData _getNotificationTypeIcon(String type) { + switch (type.toLowerCase()) { + case 'academic': + return Icons.school; + case 'fee': + return Icons.payment; + case 'exam': + return Icons.quiz; + case 'event': + return Icons.event; + case 'announcement': + return Icons.campaign; + case 'system': + return Icons.settings; + default: + return Icons.notifications; + } + } + + Color _getNotificationTypeColor(String type) { + switch (type.toLowerCase()) { + case 'academic': + return Colors.blue; + case 'fee': + return Colors.green; + case 'exam': + return Colors.orange; + case 'event': + return Colors.purple; + case 'announcement': + return Colors.red; + case 'system': + return Colors.grey; + default: + return const Color(0xFF6366F1); + } + } + + void _showNotificationDetails( + BuildContext context, + NotificationModel notification, + ) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: + (context) => Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + child: DraggableScrollableSheet( + initialChildSize: 0.6, + maxChildSize: 0.9, + minChildSize: 0.3, + builder: (context, scrollController) { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Handle + Center( + child: Container( + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + const SizedBox(height: 20), + // Header + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: _getNotificationTypeColor( + notification.type, + ).withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + _getNotificationTypeIcon(notification.type), + color: _getNotificationTypeColor( + notification.type, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + notification.title, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + Text( + timeago.format(notification.createdAt), + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + ], + ), + ), + _buildNotificationTypeChip(notification.type), + ], + ), + const SizedBox(height: 20), + // Content + Expanded( + child: SingleChildScrollView( + controller: scrollController, + child: Text( + notification.message, + style: const TextStyle(fontSize: 16, height: 1.6), + ), + ), + ), + ], + ), + ); + }, + ), + ), + ); + } + + void _showDeleteConfirmation( + BuildContext context, + NotificationModel notification, + NotificationsController controller, + ) { + showDialog( + context: context, + builder: + (context) => AlertDialog( + title: const Text('Delete Notification'), + content: const Text( + 'Are you sure you want to delete this notification?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + controller.deleteNotification(notification.id); + }, + child: const Text('Delete'), + ), + ], + ), + ); + } +} diff --git a/lib/views/assigned_courses_screen.dart b/lib/views/assigned_courses_screen.dart index 3524fbf..fc33ec0 100644 --- a/lib/views/assigned_courses_screen.dart +++ b/lib/views/assigned_courses_screen.dart @@ -1,767 +1,767 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:cloud_firestore/cloud_firestore.dart'; - -class AssignedCoursesScreen extends StatefulWidget { - const AssignedCoursesScreen({Key? key}) : super(key: key); - - @override - State createState() => _AssignedCoursesScreenState(); -} - -class _AssignedCoursesScreenState extends State - with TickerProviderStateMixin { - late AnimationController _fadeController; - late AnimationController _staggerController; - late Animation _fadeAnimation; - late Animation _scaleAnimation; - - @override - void initState() { - super.initState(); - - _fadeController = AnimationController( - duration: const Duration(milliseconds: 800), - vsync: this, - ); - - _staggerController = AnimationController( - duration: const Duration(milliseconds: 1200), - vsync: this, - ); - - _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), - ); - - _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( - CurvedAnimation(parent: _fadeController, curve: Curves.elasticOut), - ); - - _fadeController.forward(); - } - - @override - void dispose() { - _fadeController.dispose(); - _staggerController.dispose(); - super.dispose(); - } - - Future>> _fetchCourses() async { - final snapshot = - await FirebaseFirestore.instance.collection('courses/main/6th').get(); - return snapshot.docs.map((doc) => doc.data()).toList(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - body: CustomScrollView( - slivers: [ - _buildAnimatedAppBar(), - SliverToBoxAdapter( - child: FadeTransition( - opacity: _fadeAnimation, - child: ScaleTransition( - scale: _scaleAnimation, - child: _buildCoursesList(), - ), - ), - ), - ], - ), - ); - } - - Widget _buildAnimatedAppBar() { - return SliverAppBar( - expandedHeight: 200, - floating: false, - pinned: true, - elevation: 0, - backgroundColor: Colors.transparent, - flexibleSpace: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - const Color(0xFF6366F1), - const Color(0xFF8B5CF6), - const Color(0xFFA855F7), - ], - ), - ), - child: FlexibleSpaceBar( - title: const Text( - 'Assigned Courses', - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - background: Stack( - children: [ - // Animated background patterns - Positioned( - top: -50, - right: -50, - child: _buildFloatingIcon(Icons.school, 100, 0.1), - ), - Positioned( - top: 50, - left: -30, - child: _buildFloatingIcon(Icons.menu_book, 80, 0.08), - ), - Positioned( - bottom: 20, - right: 20, - child: _buildFloatingIcon(Icons.assignment, 60, 0.12), - ), - ], - ), - ), - ), - ); - } - - Widget _buildFloatingIcon(IconData icon, double size, double opacity) { - return TweenAnimationBuilder( - duration: const Duration(seconds: 3), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return Transform.rotate( - angle: value * 0.1, - child: Transform.translate( - offset: Offset(0, 10 * (0.5 - (value % 1.0).abs())), - child: Icon( - icon, - size: size, - color: Colors.white.withOpacity(opacity), - ), - ), - ); - }, - ); - } - - Widget _buildCoursesList() { - return FutureBuilder>>( - future: _fetchCourses(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return _buildShimmerLoading(); - } else if (snapshot.hasError) { - return _buildErrorState(snapshot.error.toString()); - } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - return _buildEmptyState(); - } - - final courses = snapshot.data!; - _staggerController.forward(); - - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildSectionHeader(courses.length), - const SizedBox(height: 20), - ...courses.asMap().entries.map((entry) { - final index = entry.key; - final course = entry.value; - return _buildAnimatedCourseCard(course, index); - }).toList(), - ], - ), - ); - }, - ); - } - - Widget _buildSectionHeader(int courseCount) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - const Color(0xFF6366F1).withOpacity(0.1), - const Color(0xFF8B5CF6).withOpacity(0.05), - ], - ), - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: const Color(0xFF6366F1).withOpacity(0.2), - width: 1, - ), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [const Color(0xFF6366F1), const Color(0xFF8B5CF6)], - ), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: const Color(0xFF6366F1).withOpacity(0.3), - blurRadius: 10, - offset: const Offset(0, 4), - ), - ], - ), - child: const Icon(Icons.school, color: Colors.white, size: 24), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Your Courses', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.titleMedium?.color, - ), - ), - const SizedBox(height: 4), - Text( - '$courseCount ${courseCount == 1 ? 'course' : 'courses'} enrolled', - style: TextStyle( - fontSize: 14, - color: Theme.of( - context, - ).textTheme.bodyMedium?.color?.withOpacity(0.7), - ), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildAnimatedCourseCard(Map course, int index) { - return AnimatedBuilder( - animation: _staggerController, - builder: (context, child) { - final delay = index * 0.1; - final animationValue = Curves.easeOutCubic.transform( - (_staggerController.value - delay).clamp(0.0, 1.0), - ); - - return Transform.translate( - offset: Offset(0, 50 * (1 - animationValue)), - child: Opacity(opacity: animationValue, child: child!), - ); - }, - child: Container( - margin: const EdgeInsets.only(bottom: 20), - child: _buildCourseCard(course, index), - ), - ); - } - - Widget _buildCourseCard(Map course, int index) { - final colors = [ - [const Color(0xFF6366F1), const Color(0xFF8B5CF6)], - [const Color(0xFF10B981), const Color(0xFF059669)], - [const Color(0xFFF59E0B), const Color(0xFFD97706)], - [const Color(0xFFEF4444), const Color(0xFFDC2626)], - [const Color(0xFF8B5CF6), const Color(0xFF7C3AED)], - [const Color(0xFF06B6D4), const Color(0xFF0891B2)], - ]; - - final colorPair = colors[index % colors.length]; - - return GestureDetector( - onTap: () { - // Add haptic feedback - HapticFeedback.lightImpact(); - // Navigate to course details - _showCourseDetails(course); - }, - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: colorPair, - ), - borderRadius: BorderRadius.circular(24), - boxShadow: [ - BoxShadow( - color: colorPair[0].withOpacity(0.3), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(24), - child: Stack( - children: [ - // Background pattern - Positioned( - top: -50, - right: -50, - child: Icon( - Icons.school, - size: 120, - color: Colors.white.withOpacity(0.1), - ), - ), - - // Content - Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - _getCourseIcon(course['code'] ?? ''), - color: Colors.white, - size: 24, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - course['name'] ?? 'Unknown Course', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 4), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - course['code'] ?? 'N/A', - style: const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - ), - ], - ), - ), - ], - ), - - if (course['teachers'] != null) ...[ - const SizedBox(height: 20), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), - borderRadius: BorderRadius.circular(16), - ), - child: Row( - children: [ - const Icon( - Icons.person, - color: Colors.white, - size: 20, - ), - const SizedBox(width: 12), - Expanded( - child: Text( - 'Instructors: ${(course['teachers'] as List).join(', ')}', - style: const TextStyle( - fontSize: 14, - color: Colors.white, - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ), - ], - - const SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildActionButton( - 'View Details', - Icons.visibility, - () => _showCourseDetails(course), - ), - _buildActionButton( - 'Resources', - Icons.library_books, - () => _showResources(course), - ), - ], - ), - ], - ), - ), - ], - ), - ), - ), - ); - } - - Widget _buildActionButton(String text, IconData icon, VoidCallback onTap) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(20), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(icon, color: Colors.white, size: 16), - const SizedBox(width: 8), - Text( - text, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ); - } - - IconData _getCourseIcon(String courseCode) { - final code = courseCode.toLowerCase(); - if (code.contains('cse') || code.contains('cs') || code.contains('prog')) { - return Icons.computer; - } else if (code.contains('math') || code.contains('calc')) { - return Icons.calculate; - } else if (code.contains('eng') || code.contains('english')) { - return Icons.language; - } else if (code.contains('phy') || code.contains('physics')) { - return Icons.science; - } else if (code.contains('chem') || code.contains('chemistry')) { - return Icons.biotech; - } else { - return Icons.menu_book; - } - } - - Widget _buildShimmerLoading() { - return Padding( - padding: const EdgeInsets.all(20), - child: Column(children: List.generate(3, (index) => _buildShimmerCard())), - ); - } - - Widget _buildShimmerCard() { - return Container( - margin: const EdgeInsets.only(bottom: 20), - height: 200, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.grey[300]!, Colors.grey[100]!], - ), - ), - child: TweenAnimationBuilder( - duration: const Duration(milliseconds: 1500), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return ShaderMask( - shaderCallback: (bounds) { - return LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.grey[300]!, Colors.white, Colors.grey[300]!], - stops: [value - 0.3, value, value + 0.3], - ).createShader(bounds); - }, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(24), - ), - ), - ); - }, - ), - ); - } - - Widget _buildEmptyState() { - return Center( - child: Padding( - padding: const EdgeInsets.all(40), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TweenAnimationBuilder( - duration: const Duration(seconds: 2), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return Transform.scale( - scale: 0.8 + (0.2 * value), - child: Icon( - Icons.school_outlined, - size: 100, - color: const Color(0xFF6366F1).withOpacity(0.3), - ), - ); - }, - ), - const SizedBox(height: 24), - Text( - 'No Courses Assigned', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.titleMedium?.color, - ), - ), - const SizedBox(height: 12), - Text( - 'Your assigned courses will appear here once they are available.', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of( - context, - ).textTheme.bodyMedium?.color?.withOpacity(0.7), - ), - ), - ], - ), - ), - ); - } - - Widget _buildErrorState(String error) { - return Center( - child: Padding( - padding: const EdgeInsets.all(40), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.error_outline, - size: 80, - color: Theme.of(context).colorScheme.error, - ), - const SizedBox(height: 24), - Text( - 'Something went wrong', - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.titleMedium?.color, - ), - ), - const SizedBox(height: 12), - Text( - 'Please try again later', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16, - color: Theme.of( - context, - ).textTheme.bodyMedium?.color?.withOpacity(0.7), - ), - ), - ], - ), - ), - ); - } - - void _showCourseDetails(Map course) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => _buildCourseDetailsModal(course), - ); - } - - void _showResources(Map course) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Resources for ${course['name']} coming soon!'), - backgroundColor: const Color(0xFF6366F1), - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - ), - ); - } - - Widget _buildCourseDetailsModal(Map course) { - return Container( - height: MediaQuery.of(context).size.height * 0.7, - decoration: BoxDecoration( - color: Theme.of(context).scaffoldBackgroundColor, - borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), - ), - child: Column( - children: [ - Container( - margin: const EdgeInsets.symmetric(vertical: 12), - width: 40, - height: 4, - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(2), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - course['name'] ?? 'Unknown Course', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Theme.of(context).textTheme.titleMedium?.color, - ), - ), - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - decoration: BoxDecoration( - color: const Color(0xFF6366F1).withOpacity(0.1), - borderRadius: BorderRadius.circular(20), - ), - child: Text( - course['code'] ?? 'N/A', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Color(0xFF6366F1), - ), - ), - ), - const SizedBox(height: 32), - if (course['teachers'] != null) ...[ - _buildDetailSection( - 'Instructors', - Icons.person, - (course['teachers'] as List).join(', '), - ), - const SizedBox(height: 24), - ], - _buildDetailSection( - 'Course Code', - Icons.code, - course['code'] ?? 'N/A', - ), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildDetailSection(String title, IconData icon, String content) { - return Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: const Color(0xFF6366F1).withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: Icon(icon, color: const Color(0xFF6366F1), size: 24), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Theme.of( - context, - ).textTheme.bodyMedium?.color?.withOpacity(0.7), - ), - ), - const SizedBox(height: 4), - Text( - content, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).textTheme.titleMedium?.color, - ), - ), - ], - ), - ), - ], - ), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +class AssignedCoursesScreen extends StatefulWidget { + const AssignedCoursesScreen({Key? key}) : super(key: key); + + @override + State createState() => _AssignedCoursesScreenState(); +} + +class _AssignedCoursesScreenState extends State + with TickerProviderStateMixin { + late AnimationController _fadeController; + late AnimationController _staggerController; + late Animation _fadeAnimation; + late Animation _scaleAnimation; + + @override + void initState() { + super.initState(); + + _fadeController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + + _staggerController = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + + _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut), + ); + + _scaleAnimation = Tween(begin: 0.8, end: 1.0).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.elasticOut), + ); + + _fadeController.forward(); + } + + @override + void dispose() { + _fadeController.dispose(); + _staggerController.dispose(); + super.dispose(); + } + + Future>> _fetchCourses() async { + final snapshot = + await FirebaseFirestore.instance.collection('courses/main/6th').get(); + return snapshot.docs.map((doc) => doc.data()).toList(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + body: CustomScrollView( + slivers: [ + _buildAnimatedAppBar(), + SliverToBoxAdapter( + child: FadeTransition( + opacity: _fadeAnimation, + child: ScaleTransition( + scale: _scaleAnimation, + child: _buildCoursesList(), + ), + ), + ), + ], + ), + ); + } + + Widget _buildAnimatedAppBar() { + return SliverAppBar( + expandedHeight: 200, + floating: false, + pinned: true, + elevation: 0, + backgroundColor: Colors.transparent, + flexibleSpace: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFF6366F1), + const Color(0xFF8B5CF6), + const Color(0xFFA855F7), + ], + ), + ), + child: FlexibleSpaceBar( + title: const Text( + 'Assigned Courses', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + background: Stack( + children: [ + // Animated background patterns + Positioned( + top: -50, + right: -50, + child: _buildFloatingIcon(Icons.school, 100, 0.1), + ), + Positioned( + top: 50, + left: -30, + child: _buildFloatingIcon(Icons.menu_book, 80, 0.08), + ), + Positioned( + bottom: 20, + right: 20, + child: _buildFloatingIcon(Icons.assignment, 60, 0.12), + ), + ], + ), + ), + ), + ); + } + + Widget _buildFloatingIcon(IconData icon, double size, double opacity) { + return TweenAnimationBuilder( + duration: const Duration(seconds: 3), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Transform.rotate( + angle: value * 0.1, + child: Transform.translate( + offset: Offset(0, 10 * (0.5 - (value % 1.0).abs())), + child: Icon( + icon, + size: size, + color: Colors.white.withOpacity(opacity), + ), + ), + ); + }, + ); + } + + Widget _buildCoursesList() { + return FutureBuilder>>( + future: _fetchCourses(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return _buildShimmerLoading(); + } else if (snapshot.hasError) { + return _buildErrorState(snapshot.error.toString()); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + return _buildEmptyState(); + } + + final courses = snapshot.data!; + _staggerController.forward(); + + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader(courses.length), + const SizedBox(height: 20), + ...courses.asMap().entries.map((entry) { + final index = entry.key; + final course = entry.value; + return _buildAnimatedCourseCard(course, index); + }).toList(), + ], + ), + ); + }, + ); + } + + Widget _buildSectionHeader(int courseCount) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + const Color(0xFF6366F1).withOpacity(0.1), + const Color(0xFF8B5CF6).withOpacity(0.05), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: const Color(0xFF6366F1).withOpacity(0.2), + width: 1, + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [const Color(0xFF6366F1), const Color(0xFF8B5CF6)], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: const Color(0xFF6366F1).withOpacity(0.3), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: const Icon(Icons.school, color: Colors.white, size: 24), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Your Courses', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Theme.of(context).textTheme.titleMedium?.color, + ), + ), + const SizedBox(height: 4), + Text( + '$courseCount ${courseCount == 1 ? 'course' : 'courses'} enrolled', + style: TextStyle( + fontSize: 14, + color: Theme.of( + context, + ).textTheme.bodyMedium?.color?.withOpacity(0.7), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildAnimatedCourseCard(Map course, int index) { + return AnimatedBuilder( + animation: _staggerController, + builder: (context, child) { + final delay = index * 0.1; + final animationValue = Curves.easeOutCubic.transform( + (_staggerController.value - delay).clamp(0.0, 1.0), + ); + + return Transform.translate( + offset: Offset(0, 50 * (1 - animationValue)), + child: Opacity(opacity: animationValue, child: child!), + ); + }, + child: Container( + margin: const EdgeInsets.only(bottom: 20), + child: _buildCourseCard(course, index), + ), + ); + } + + Widget _buildCourseCard(Map course, int index) { + final colors = [ + [const Color(0xFF6366F1), const Color(0xFF8B5CF6)], + [const Color(0xFF10B981), const Color(0xFF059669)], + [const Color(0xFFF59E0B), const Color(0xFFD97706)], + [const Color(0xFFEF4444), const Color(0xFFDC2626)], + [const Color(0xFF8B5CF6), const Color(0xFF7C3AED)], + [const Color(0xFF06B6D4), const Color(0xFF0891B2)], + ]; + + final colorPair = colors[index % colors.length]; + + return GestureDetector( + onTap: () { + // Add haptic feedback + HapticFeedback.lightImpact(); + // Navigate to course details + _showCourseDetails(course); + }, + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: colorPair, + ), + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: colorPair[0].withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Stack( + children: [ + // Background pattern + Positioned( + top: -50, + right: -50, + child: Icon( + Icons.school, + size: 120, + color: Colors.white.withOpacity(0.1), + ), + ), + + // Content + Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + _getCourseIcon(course['code'] ?? ''), + color: Colors.white, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + course['name'] ?? 'Unknown Course', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + course['code'] ?? 'N/A', + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ], + ), + + if (course['teachers'] != null) ...[ + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(16), + ), + child: Row( + children: [ + const Icon( + Icons.person, + color: Colors.white, + size: 20, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Instructors: ${(course['teachers'] as List).join(', ')}', + style: const TextStyle( + fontSize: 14, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ], + + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildActionButton( + 'View Details', + Icons.visibility, + () => _showCourseDetails(course), + ), + _buildActionButton( + 'Resources', + Icons.library_books, + () => _showResources(course), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildActionButton(String text, IconData icon, VoidCallback onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, color: Colors.white, size: 16), + const SizedBox(width: 8), + Text( + text, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ); + } + + IconData _getCourseIcon(String courseCode) { + final code = courseCode.toLowerCase(); + if (code.contains('cse') || code.contains('cs') || code.contains('prog')) { + return Icons.computer; + } else if (code.contains('math') || code.contains('calc')) { + return Icons.calculate; + } else if (code.contains('eng') || code.contains('english')) { + return Icons.language; + } else if (code.contains('phy') || code.contains('physics')) { + return Icons.science; + } else if (code.contains('chem') || code.contains('chemistry')) { + return Icons.biotech; + } else { + return Icons.menu_book; + } + } + + Widget _buildShimmerLoading() { + return Padding( + padding: const EdgeInsets.all(20), + child: Column(children: List.generate(3, (index) => _buildShimmerCard())), + ); + } + + Widget _buildShimmerCard() { + return Container( + margin: const EdgeInsets.only(bottom: 20), + height: 200, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.grey[300]!, Colors.grey[100]!], + ), + ), + child: TweenAnimationBuilder( + duration: const Duration(milliseconds: 1500), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return ShaderMask( + shaderCallback: (bounds) { + return LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.grey[300]!, Colors.white, Colors.grey[300]!], + stops: [value - 0.3, value, value + 0.3], + ).createShader(bounds); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + ), + ), + ); + }, + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TweenAnimationBuilder( + duration: const Duration(seconds: 2), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Transform.scale( + scale: 0.8 + (0.2 * value), + child: Icon( + Icons.school_outlined, + size: 100, + color: const Color(0xFF6366F1).withOpacity(0.3), + ), + ); + }, + ), + const SizedBox(height: 24), + Text( + 'No Courses Assigned', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Theme.of(context).textTheme.titleMedium?.color, + ), + ), + const SizedBox(height: 12), + Text( + 'Your assigned courses will appear here once they are available.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of( + context, + ).textTheme.bodyMedium?.color?.withOpacity(0.7), + ), + ), + ], + ), + ), + ); + } + + Widget _buildErrorState(String error) { + return Center( + child: Padding( + padding: const EdgeInsets.all(40), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.error_outline, + size: 80, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(height: 24), + Text( + 'Something went wrong', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Theme.of(context).textTheme.titleMedium?.color, + ), + ), + const SizedBox(height: 12), + Text( + 'Please try again later', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + color: Theme.of( + context, + ).textTheme.bodyMedium?.color?.withOpacity(0.7), + ), + ), + ], + ), + ), + ); + } + + void _showCourseDetails(Map course) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => _buildCourseDetailsModal(course), + ); + } + + void _showResources(Map course) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Resources for ${course['name']} coming soon!'), + backgroundColor: const Color(0xFF6366F1), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + ), + ); + } + + Widget _buildCourseDetailsModal(Map course) { + return Container( + height: MediaQuery.of(context).size.height * 0.7, + decoration: BoxDecoration( + color: Theme.of(context).scaffoldBackgroundColor, + borderRadius: const BorderRadius.vertical(top: Radius.circular(30)), + ), + child: Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 12), + width: 40, + height: 4, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + course['name'] ?? 'Unknown Course', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Theme.of(context).textTheme.titleMedium?.color, + ), + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: const Color(0xFF6366F1).withOpacity(0.1), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + course['code'] ?? 'N/A', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Color(0xFF6366F1), + ), + ), + ), + const SizedBox(height: 32), + if (course['teachers'] != null) ...[ + _buildDetailSection( + 'Instructors', + Icons.person, + (course['teachers'] as List).join(', '), + ), + const SizedBox(height: 24), + ], + _buildDetailSection( + 'Course Code', + Icons.code, + course['code'] ?? 'N/A', + ), + ], + ), + ), + ), + ], + ), + ); + } + + Widget _buildDetailSection(String title, IconData icon, String content) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFF6366F1).withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Icon(icon, color: const Color(0xFF6366F1), size: 24), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Theme.of( + context, + ).textTheme.bodyMedium?.color?.withOpacity(0.7), + ), + ), + const SizedBox(height: 4), + Text( + content, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).textTheme.titleMedium?.color, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/authgate.dart b/lib/views/authgate.dart index 73ca327..584ff79 100644 --- a/lib/views/authgate.dart +++ b/lib/views/authgate.dart @@ -1,29 +1,29 @@ -import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/material.dart'; -import 'package:myedu/controllers/auth_controller.dart'; -import 'package:myedu/views/dashboard_view.dart'; -import 'package:myedu/views/sign_in_view.dart'; - -class Authgate extends StatelessWidget { - const Authgate({super.key}); - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: FirebaseAuth.instance.authStateChanges(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.active) { - final user = snapshot.data; - if (user == null) { - return const SignInView(); - } else { - AuthController.instance.loadUserData(); - return const DashboardView(); - } - } else { - return const Center(child: CircularProgressIndicator()); - } - }, - ); - } -} +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:myedu/controllers/auth_controller.dart'; +import 'package:myedu/views/dashboard_view.dart'; +import 'package:myedu/views/sign_in_view.dart'; + +class Authgate extends StatelessWidget { + const Authgate({super.key}); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.active) { + final user = snapshot.data; + if (user == null) { + return const SignInView(); + } else { + AuthController.instance.loadUserData(); + return const DashboardView(); + } + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ); + } +} diff --git a/lib/views/class_schedule_screen.dart b/lib/views/class_schedule_screen.dart index 27c65bd..aec796b 100644 --- a/lib/views/class_schedule_screen.dart +++ b/lib/views/class_schedule_screen.dart @@ -1,647 +1,647 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:get/get.dart'; -import '../controllers/class_schedule_controller.dart'; -import '../models/class_schedule_model.dart'; - -class ClassScheduleScreen extends StatelessWidget { - const ClassScheduleScreen({super.key}); - - @override - Widget build(BuildContext context) { - final controller = Get.put(ClassScheduleController()); - - return Scaffold( - backgroundColor: const Color(0xFFF8FAFC), - body: Obx(() { - if (controller.isLoading.value) { - return _buildLoadingState(); - } - - if (controller.errorMessage.value.isNotEmpty) { - return _buildErrorState(context, controller); - } - - return NestedScrollView( - headerSliverBuilder: - (context, innerBoxIsScrolled) => [ - _buildAnimatedAppBar(context, controller), - ], - body: RefreshIndicator( - onRefresh: () => controller.fetchClassSchedule(), - color: const Color(0xFF6366F1), - child: AnimationLimiter( - child: ListView( - padding: const EdgeInsets.all(20), - children: AnimationConfiguration.toStaggeredList( - duration: const Duration(milliseconds: 600), - childAnimationBuilder: - (widget) => SlideAnimation( - horizontalOffset: 50.0, - child: FadeInAnimation(child: widget), - ), - children: [ - _buildTodaySection(context, controller), - const SizedBox(height: 32), - _buildTomorrowSection(context, controller), - const SizedBox(height: 40), - ], - ), - ), - ), - ), - ); - }), - ); - } - - Widget _buildAnimatedAppBar( - BuildContext context, - ClassScheduleController controller, - ) { - return SliverAppBar( - expandedHeight: 160, - floating: false, - pinned: true, - elevation: 0, - backgroundColor: const Color(0xFF6366F1), - flexibleSpace: FlexibleSpaceBar( - titlePadding: const EdgeInsets.only(left: 20, bottom: 16), - title: AnimatedContainer( - duration: const Duration(milliseconds: 300), - child: const Text( - 'Class Schedule', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white, - fontSize: 24, - ), - ), - ), - background: Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF6366F1), Color(0xFF8B5CF6), Color(0xFFA855F7)], - ), - ), - child: Stack( - children: [ - Positioned( - top: -20, - right: -20, - child: Container( - width: 120, - height: 120, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.1), - ), - ), - ), - Positioned( - bottom: -30, - left: -30, - child: Container( - width: 80, - height: 80, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.1), - ), - ), - ), - ], - ), - ), - ), - actions: [ - IconButton( - icon: const Icon(Icons.refresh_rounded, color: Colors.white), - onPressed: () => controller.refresh(), - ), - ], - ); - } - - Widget _buildLoadingState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 20, - offset: const Offset(0, 10), - ), - ], - ), - child: const CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Color(0xFF6366F1)), - strokeWidth: 3, - ), - ), - const SizedBox(height: 24), - const Text( - 'Loading your schedule...', - style: TextStyle( - fontSize: 16, - color: Color(0xFF64748B), - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ); - } - - Widget _buildErrorState( - BuildContext context, - ClassScheduleController controller, - ) { - return Center( - child: AnimationConfiguration.staggeredList( - position: 0, - duration: const Duration(milliseconds: 600), - child: SlideAnimation( - verticalOffset: 50.0, - child: FadeInAnimation( - child: Container( - margin: const EdgeInsets.all(20), - padding: const EdgeInsets.all(32), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(24), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.08), - blurRadius: 30, - offset: const Offset(0, 15), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color(0xFFEF4444).withOpacity(0.1), - shape: BoxShape.circle, - ), - child: const Icon( - Icons.error_outline_rounded, - size: 48, - color: Color(0xFFEF4444), - ), - ), - const SizedBox(height: 24), - Text( - 'Oops! Something went wrong', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Color(0xFF1E293B), - ), - ), - const SizedBox(height: 12), - Text( - controller.errorMessage.value, - style: const TextStyle( - fontSize: 16, - color: Color(0xFF64748B), - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 32), - ElevatedButton( - onPressed: () => controller.refresh(), - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF6366F1), - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - horizontal: 32, - vertical: 16, - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - elevation: 0, - ), - child: const Text('Try Again'), - ), - ], - ), - ), - ), - ), - ), - ); - } - - Widget _buildTodaySection( - BuildContext context, - ClassScheduleController controller, - ) { - return Column( - children: [ - _buildSectionHeader( - 'Today\'s Classes', - '${controller.getCurrentDayName()}, ${_getFormattedDate(DateTime.now())}', - Icons.today_rounded, - const Color(0xFF6366F1), - true, - ), - const SizedBox(height: 20), - _buildClassList(controller.getTodaysClasses(), true), - ], - ); - } - - Widget _buildTomorrowSection( - BuildContext context, - ClassScheduleController controller, - ) { - return Column( - children: [ - _buildSectionHeader( - 'Tomorrow\'s Classes', - '${controller.getTomorrowDayName()}, ${_getFormattedDate(DateTime.now().add(const Duration(days: 1)))}', - Icons.calendar_today_rounded, - const Color(0xFF10B981), - false, - ), - const SizedBox(height: 20), - _buildClassList(controller.getTomorrowsClasses(), false), - ], - ); - } - - Widget _buildSectionHeader( - String title, - String subtitle, - IconData icon, - Color color, - bool isToday, - ) { - return Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [color.withOpacity(0.1), color.withOpacity(0.05)], - ), - borderRadius: BorderRadius.circular(20), - border: Border.all(color: color.withOpacity(0.2), width: 1), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [color, color.withOpacity(0.8)], - ), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: color.withOpacity(0.3), - blurRadius: 12, - offset: const Offset(0, 6), - ), - ], - ), - child: Icon(icon, color: Colors.white, size: 28), - ), - const SizedBox(width: 20), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Color(0xFF1E293B), - ), - ), - const SizedBox(height: 6), - Text( - subtitle, - style: const TextStyle( - fontSize: 15, - color: Color(0xFF64748B), - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildClassList(List classes, bool isToday) { - if (classes.isEmpty) { - return _buildEmptyState(isToday); - } - - // Sort classes by time - classes.sort((a, b) { - DateTime? timeA = a.getStartTime(); - DateTime? timeB = b.getStartTime(); - if (timeA != null && timeB != null) { - return timeA.compareTo(timeB); - } - return 0; - }); - - return AnimationLimiter( - child: Column( - children: List.generate( - classes.length, - (index) => AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 30.0, - child: FadeInAnimation( - child: Container( - margin: const EdgeInsets.only(bottom: 16), - child: _buildClassCard(classes[index], isToday), - ), - ), - ), - ), - ), - ), - ); - } - - Widget _buildEmptyState(bool isToday) { - return Container( - padding: const EdgeInsets.all(40), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.06), - blurRadius: 20, - offset: const Offset(0, 8), - ), - ], - ), - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: const Color(0xFF10B981).withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.free_breakfast_rounded, - size: 48, - color: const Color(0xFF10B981), - ), - ), - const SizedBox(height: 20), - const Text( - 'No classes scheduled', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF1E293B), - ), - ), - const SizedBox(height: 8), - Text( - isToday ? 'Enjoy your free day!' : 'No classes tomorrow', - style: const TextStyle(fontSize: 15, color: Color(0xFF64748B)), - ), - ], - ), - ); - } - - Widget _buildClassCard(TimeSlot timeSlot, bool isToday) { - bool isCurrent = isToday && timeSlot.isCurrentClass(); - bool isUpcoming = isToday && timeSlot.isUpcoming(); - - Color primaryColor = const Color(0xFF6366F1); - Color currentColor = const Color(0xFF10B981); - Color upcomingColor = const Color(0xFFF59E0B); - - if (isCurrent) { - primaryColor = currentColor; - } else if (isUpcoming) { - primaryColor = upcomingColor; - } - - return Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: - isCurrent || isUpcoming - ? primaryColor.withOpacity(0.3) - : Colors.transparent, - width: 2, - ), - boxShadow: [ - BoxShadow( - color: - (isCurrent || isUpcoming - ? primaryColor.withOpacity(0.2) - : Colors.black.withOpacity(0.06)), - blurRadius: isCurrent || isUpcoming ? 20 : 15, - offset: const Offset(0, 8), - ), - ], - ), - child: Material( - color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(20), - onTap: () { - // Add any tap functionality here - }, - child: Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - // Time Section - Container( - padding: const EdgeInsets.symmetric( - vertical: 16, - horizontal: 20, - ), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [primaryColor, primaryColor.withOpacity(0.8)], - ), - borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: primaryColor.withOpacity(0.3), - blurRadius: 12, - offset: const Offset(0, 6), - ), - ], - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - _getStartTime(timeSlot.timeRange), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 4), - Container( - width: 20, - height: 2, - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.5), - borderRadius: BorderRadius.circular(1), - ), - ), - const SizedBox(height: 4), - Text( - _getEndTime(timeSlot.timeRange), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white.withOpacity(0.9), - ), - ), - ], - ), - ), - const SizedBox(width: 20), - - // Course Details - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - timeSlot.subject, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Color(0xFF1E293B), - ), - ), - ), - if (isCurrent) - _buildStatusBadge('LIVE', currentColor), - if (isUpcoming) - _buildStatusBadge('NEXT', upcomingColor), - ], - ), - if (timeSlot.code.isNotEmpty) ...[ - const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, - ), - decoration: BoxDecoration( - color: const Color(0xFF6366F1).withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Text( - timeSlot.code, - style: const TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: Color(0xFF6366F1), - ), - ), - ), - ], - ], - ), - ), - ], - ), - ), - ), - ), - ); - } - - Widget _buildStatusBadge(String text, Color color) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [color, color.withOpacity(0.8)], - ), - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: color.withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ], - ), - child: Text( - text, - style: const TextStyle( - color: Colors.white, - fontSize: 11, - fontWeight: FontWeight.bold, - letterSpacing: 0.5, - ), - ), - ); - } - - String _getFormattedDate(DateTime date) { - return '${date.day}/${date.month}/${date.year}'; - } - - String _getStartTime(String timeRange) { - if (timeRange.contains('-')) { - return timeRange.split('-')[0].trim(); - } - return timeRange.trim(); - } - - String _getEndTime(String timeRange) { - if (timeRange.contains('-')) { - final parts = timeRange.split('-'); - if (parts.length > 1) { - return parts[1].trim(); - } - } - return ''; - } -} +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:get/get.dart'; +import '../controllers/class_schedule_controller.dart'; +import '../models/class_schedule_model.dart'; + +class ClassScheduleScreen extends StatelessWidget { + const ClassScheduleScreen({super.key}); + + @override + Widget build(BuildContext context) { + final controller = Get.put(ClassScheduleController()); + + return Scaffold( + backgroundColor: const Color(0xFFF8FAFC), + body: Obx(() { + if (controller.isLoading.value) { + return _buildLoadingState(); + } + + if (controller.errorMessage.value.isNotEmpty) { + return _buildErrorState(context, controller); + } + + return NestedScrollView( + headerSliverBuilder: + (context, innerBoxIsScrolled) => [ + _buildAnimatedAppBar(context, controller), + ], + body: RefreshIndicator( + onRefresh: () => controller.fetchClassSchedule(), + color: const Color(0xFF6366F1), + child: AnimationLimiter( + child: ListView( + padding: const EdgeInsets.all(20), + children: AnimationConfiguration.toStaggeredList( + duration: const Duration(milliseconds: 600), + childAnimationBuilder: + (widget) => SlideAnimation( + horizontalOffset: 50.0, + child: FadeInAnimation(child: widget), + ), + children: [ + _buildTodaySection(context, controller), + const SizedBox(height: 32), + _buildTomorrowSection(context, controller), + const SizedBox(height: 40), + ], + ), + ), + ), + ), + ); + }), + ); + } + + Widget _buildAnimatedAppBar( + BuildContext context, + ClassScheduleController controller, + ) { + return SliverAppBar( + expandedHeight: 160, + floating: false, + pinned: true, + elevation: 0, + backgroundColor: const Color(0xFF6366F1), + flexibleSpace: FlexibleSpaceBar( + titlePadding: const EdgeInsets.only(left: 20, bottom: 16), + title: AnimatedContainer( + duration: const Duration(milliseconds: 300), + child: const Text( + 'Class Schedule', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 24, + ), + ), + ), + background: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF6366F1), Color(0xFF8B5CF6), Color(0xFFA855F7)], + ), + ), + child: Stack( + children: [ + Positioned( + top: -20, + right: -20, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.1), + ), + ), + ), + Positioned( + bottom: -30, + left: -30, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.1), + ), + ), + ), + ], + ), + ), + ), + actions: [ + IconButton( + icon: const Icon(Icons.refresh_rounded, color: Colors.white), + onPressed: () => controller.refresh(), + ), + ], + ); + } + + Widget _buildLoadingState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Color(0xFF6366F1)), + strokeWidth: 3, + ), + ), + const SizedBox(height: 24), + const Text( + 'Loading your schedule...', + style: TextStyle( + fontSize: 16, + color: Color(0xFF64748B), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } + + Widget _buildErrorState( + BuildContext context, + ClassScheduleController controller, + ) { + return Center( + child: AnimationConfiguration.staggeredList( + position: 0, + duration: const Duration(milliseconds: 600), + child: SlideAnimation( + verticalOffset: 50.0, + child: FadeInAnimation( + child: Container( + margin: const EdgeInsets.all(20), + padding: const EdgeInsets.all(32), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.08), + blurRadius: 30, + offset: const Offset(0, 15), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFFEF4444).withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.error_outline_rounded, + size: 48, + color: Color(0xFFEF4444), + ), + ), + const SizedBox(height: 24), + Text( + 'Oops! Something went wrong', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Color(0xFF1E293B), + ), + ), + const SizedBox(height: 12), + Text( + controller.errorMessage.value, + style: const TextStyle( + fontSize: 16, + color: Color(0xFF64748B), + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 32), + ElevatedButton( + onPressed: () => controller.refresh(), + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF6366F1), + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + elevation: 0, + ), + child: const Text('Try Again'), + ), + ], + ), + ), + ), + ), + ), + ); + } + + Widget _buildTodaySection( + BuildContext context, + ClassScheduleController controller, + ) { + return Column( + children: [ + _buildSectionHeader( + 'Today\'s Classes', + '${controller.getCurrentDayName()}, ${_getFormattedDate(DateTime.now())}', + Icons.today_rounded, + const Color(0xFF6366F1), + true, + ), + const SizedBox(height: 20), + _buildClassList(controller.getTodaysClasses(), true), + ], + ); + } + + Widget _buildTomorrowSection( + BuildContext context, + ClassScheduleController controller, + ) { + return Column( + children: [ + _buildSectionHeader( + 'Tomorrow\'s Classes', + '${controller.getTomorrowDayName()}, ${_getFormattedDate(DateTime.now().add(const Duration(days: 1)))}', + Icons.calendar_today_rounded, + const Color(0xFF10B981), + false, + ), + const SizedBox(height: 20), + _buildClassList(controller.getTomorrowsClasses(), false), + ], + ); + } + + Widget _buildSectionHeader( + String title, + String subtitle, + IconData icon, + Color color, + bool isToday, + ) { + return Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [color.withOpacity(0.1), color.withOpacity(0.05)], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all(color: color.withOpacity(0.2), width: 1), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [color, color.withOpacity(0.8)], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.3), + blurRadius: 12, + offset: const Offset(0, 6), + ), + ], + ), + child: Icon(icon, color: Colors.white, size: 28), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Color(0xFF1E293B), + ), + ), + const SizedBox(height: 6), + Text( + subtitle, + style: const TextStyle( + fontSize: 15, + color: Color(0xFF64748B), + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildClassList(List classes, bool isToday) { + if (classes.isEmpty) { + return _buildEmptyState(isToday); + } + + // Sort classes by time + classes.sort((a, b) { + DateTime? timeA = a.getStartTime(); + DateTime? timeB = b.getStartTime(); + if (timeA != null && timeB != null) { + return timeA.compareTo(timeB); + } + return 0; + }); + + return AnimationLimiter( + child: Column( + children: List.generate( + classes.length, + (index) => AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 30.0, + child: FadeInAnimation( + child: Container( + margin: const EdgeInsets.only(bottom: 16), + child: _buildClassCard(classes[index], isToday), + ), + ), + ), + ), + ), + ), + ); + } + + Widget _buildEmptyState(bool isToday) { + return Container( + padding: const EdgeInsets.all(40), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.06), + blurRadius: 20, + offset: const Offset(0, 8), + ), + ], + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: const Color(0xFF10B981).withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.free_breakfast_rounded, + size: 48, + color: const Color(0xFF10B981), + ), + ), + const SizedBox(height: 20), + const Text( + 'No classes scheduled', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1E293B), + ), + ), + const SizedBox(height: 8), + Text( + isToday ? 'Enjoy your free day!' : 'No classes tomorrow', + style: const TextStyle(fontSize: 15, color: Color(0xFF64748B)), + ), + ], + ), + ); + } + + Widget _buildClassCard(TimeSlot timeSlot, bool isToday) { + bool isCurrent = isToday && timeSlot.isCurrentClass(); + bool isUpcoming = isToday && timeSlot.isUpcoming(); + + Color primaryColor = const Color(0xFF6366F1); + Color currentColor = const Color(0xFF10B981); + Color upcomingColor = const Color(0xFFF59E0B); + + if (isCurrent) { + primaryColor = currentColor; + } else if (isUpcoming) { + primaryColor = upcomingColor; + } + + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: + isCurrent || isUpcoming + ? primaryColor.withOpacity(0.3) + : Colors.transparent, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: + (isCurrent || isUpcoming + ? primaryColor.withOpacity(0.2) + : Colors.black.withOpacity(0.06)), + blurRadius: isCurrent || isUpcoming ? 20 : 15, + offset: const Offset(0, 8), + ), + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () { + // Add any tap functionality here + }, + child: Padding( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + // Time Section + Container( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 20, + ), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [primaryColor, primaryColor.withOpacity(0.8)], + ), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: primaryColor.withOpacity(0.3), + blurRadius: 12, + offset: const Offset(0, 6), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _getStartTime(timeSlot.timeRange), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 4), + Container( + width: 20, + height: 2, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.5), + borderRadius: BorderRadius.circular(1), + ), + ), + const SizedBox(height: 4), + Text( + _getEndTime(timeSlot.timeRange), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white.withOpacity(0.9), + ), + ), + ], + ), + ), + const SizedBox(width: 20), + + // Course Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + timeSlot.subject, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Color(0xFF1E293B), + ), + ), + ), + if (isCurrent) + _buildStatusBadge('LIVE', currentColor), + if (isUpcoming) + _buildStatusBadge('NEXT', upcomingColor), + ], + ), + if (timeSlot.code.isNotEmpty) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: const Color(0xFF6366F1).withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + timeSlot.code, + style: const TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Color(0xFF6366F1), + ), + ), + ), + ], + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildStatusBadge(String text, Color color) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [color, color.withOpacity(0.8)], + ), + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Text( + text, + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold, + letterSpacing: 0.5, + ), + ), + ); + } + + String _getFormattedDate(DateTime date) { + return '${date.day}/${date.month}/${date.year}'; + } + + String _getStartTime(String timeRange) { + if (timeRange.contains('-')) { + return timeRange.split('-')[0].trim(); + } + return timeRange.trim(); + } + + String _getEndTime(String timeRange) { + if (timeRange.contains('-')) { + final parts = timeRange.split('-'); + if (parts.length > 1) { + return parts[1].trim(); + } + } + return ''; + } +} diff --git a/lib/views/class_schedule_view.dart b/lib/views/class_schedule_view.dart index 8b13789..d3f5a12 100644 --- a/lib/views/class_schedule_view.dart +++ b/lib/views/class_schedule_view.dart @@ -1 +1 @@ - + diff --git a/lib/views/dashboard_view.dart b/lib/views/dashboard_view.dart index ea87805..aa35b37 100644 --- a/lib/views/dashboard_view.dart +++ b/lib/views/dashboard_view.dart @@ -2,10 +2,12 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:get/get.dart'; +import '../controllers/academic_result_controller.dart'; import '../controllers/auth_controller.dart'; import '../controllers/dashboard_controller.dart'; import '../controllers/theme_controller.dart'; import '../controllers/notifications_controller.dart'; +import '../models/academic_result_model.dart'; import 'id_card_view.dart'; import 'notice_screen.dart'; import 'academic_details_screen.dart'; @@ -23,6 +25,10 @@ class DashboardView extends StatelessWidget { final dashboardController = Get.put(DashboardController()); final authController = Get.find(); final notificationsController = Get.put(NotificationsController()); + final resultController = + Get.isRegistered() + ? Get.find() + : Get.put(AcademicResultController()); return Scaffold( body: CustomScrollView( @@ -58,7 +64,7 @@ class DashboardView extends StatelessWidget { height: 100, decoration: BoxDecoration( shape: BoxShape.circle, - color: Colors.white.withOpacity(0.08), + color: Colors.white.withValues(alpha: 0.08), ), ), ), @@ -71,7 +77,7 @@ class DashboardView extends StatelessWidget { height: 70, decoration: BoxDecoration( shape: BoxShape.circle, - color: Colors.white.withOpacity(0.06), + color: Colors.white.withValues(alpha: 0.06), ), ), ), @@ -84,7 +90,7 @@ class DashboardView extends StatelessWidget { height: 24, decoration: BoxDecoration( shape: BoxShape.circle, - color: Colors.white.withOpacity(0.12), + color: Colors.white.withValues(alpha: 0.12), ), ), ), @@ -96,7 +102,7 @@ class DashboardView extends StatelessWidget { height: 18, decoration: BoxDecoration( shape: BoxShape.circle, - color: Colors.white.withOpacity(0.10), + color: Colors.white.withValues(alpha: 0.10), ), ), ), @@ -109,7 +115,7 @@ class DashboardView extends StatelessWidget { child: Icon( Icons.school_outlined, size: 32, - color: Colors.white.withOpacity(0.08), + color: Colors.white.withValues(alpha: 0.08), ), ), ), @@ -121,7 +127,7 @@ class DashboardView extends StatelessWidget { child: Icon( Icons.book_outlined, size: 28, - color: Colors.white.withOpacity(0.06), + color: Colors.white.withValues(alpha: 0.06), ), ), ), @@ -135,7 +141,7 @@ class DashboardView extends StatelessWidget { width: 16, height: 16, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.08), + color: Colors.white.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(3), ), ), @@ -150,7 +156,7 @@ class DashboardView extends StatelessWidget { width: 12, height: 12, decoration: BoxDecoration( - color: Colors.white.withOpacity(0.10), + color: Colors.white.withValues(alpha: 0.10), shape: BoxShape.rectangle, ), ), @@ -169,7 +175,7 @@ class DashboardView extends StatelessWidget { height: 4, decoration: BoxDecoration( shape: BoxShape.circle, - color: Colors.white.withOpacity(0.15), + color: Colors.white.withValues(alpha: 0.15), ), ), ), @@ -187,7 +193,7 @@ class DashboardView extends StatelessWidget { height: 4, decoration: BoxDecoration( shape: BoxShape.circle, - color: Colors.white.withOpacity(0.12), + color: Colors.white.withValues(alpha: 0.12), ), ), ), @@ -219,7 +225,9 @@ class DashboardView extends StatelessWidget { // Logout button Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), + color: Colors.white.withValues( + alpha: 0.15, + ), borderRadius: BorderRadius.circular(12), ), child: IconButton( @@ -244,7 +252,9 @@ class DashboardView extends StatelessWidget { // Theme toggle button Container( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.15), + color: Colors.white.withValues( + alpha: 0.15, + ), borderRadius: BorderRadius.circular(12), ), child: GetBuilder( @@ -293,15 +303,15 @@ class DashboardView extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: Colors.white.withOpacity( - 0.8, + color: Colors.white.withValues( + alpha: 0.8, ), width: 2, ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity( - 0.15, + color: Colors.black.withValues( + alpha: 0.15, ), blurRadius: 8, offset: const Offset(0, 2), @@ -316,7 +326,7 @@ class DashboardView extends StatelessWidget { ? CircleAvatar( radius: 23, backgroundColor: Colors.white - .withOpacity(0.9), + .withValues(alpha: 0.9), child: const Icon( Icons.person, color: Color(0xFF6366F1), @@ -338,7 +348,9 @@ class DashboardView extends StatelessWidget { url, ) => Container( color: Colors.white - .withOpacity(0.9), + .withValues( + alpha: 0.9, + ), child: const SizedBox( width: 20, height: 20, @@ -364,8 +376,8 @@ class DashboardView extends StatelessWidget { radius: 23, backgroundColor: Colors.white - .withOpacity( - 0.9, + .withValues( + alpha: 0.9, ), child: const Icon( Icons.person, @@ -389,8 +401,8 @@ class DashboardView extends StatelessWidget { Text( 'Welcome back!', style: TextStyle( - color: Colors.white.withOpacity( - 0.8, + color: Colors.white.withValues( + alpha: 0.8, ), fontSize: 12, fontWeight: FontWeight.w500, @@ -413,8 +425,8 @@ class DashboardView extends StatelessWidget { authController.user?.email ?? 'No email available', style: TextStyle( - color: Colors.white.withOpacity( - 0.7, + color: Colors.white.withValues( + alpha: 0.7, ), fontSize: 14, fontWeight: FontWeight.w400, @@ -431,7 +443,7 @@ class DashboardView extends StatelessWidget { // Logout button // Container( // decoration: BoxDecoration( - // color: Colors.white.withOpacity( + // color: Colors.white.withValues(alpha: // 0.15, // ), // borderRadius: BorderRadius.circular( @@ -460,8 +472,8 @@ class DashboardView extends StatelessWidget { // Notifications button Container( decoration: BoxDecoration( - color: Colors.white.withOpacity( - 0.15, + color: Colors.white.withValues( + alpha: 0.15, ), borderRadius: BorderRadius.circular( 10, @@ -566,7 +578,7 @@ class DashboardView extends StatelessWidget { sliver: SliverList( delegate: SliverChildListDelegate([ // Quick Stats Section - _buildQuickStatsSection(context), + _buildQuickStatsSection(context, resultController), const SizedBox(height: 25), // Primary Actions Section @@ -600,7 +612,10 @@ class DashboardView extends StatelessWidget { ); } - Widget _buildQuickStatsSection(BuildContext context) { + Widget _buildQuickStatsSection( + BuildContext context, + AcademicResultController resultController, + ) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( @@ -608,48 +623,83 @@ class DashboardView extends StatelessWidget { borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 5), ), ], ), - child: Row( - children: [ - Expanded( - child: _buildStatItem( - context, - icon: Icons.school, - title: 'Current Semester', - value: '6th', - color: Colors.blue, + child: Obx(() { + final latestResult = _latestResult(resultController.academicResults); + final isLoading = resultController.isLoading.value; + + return Row( + children: [ + Expanded( + child: _buildStatItem( + context, + icon: Icons.school, + title: 'Current Semester', + value: isLoading ? '--' : latestResult?.semester ?? '--', + color: Colors.blue, + ), ), - ), - Container(width: 1, height: 50, color: Colors.grey.withOpacity(0.3)), - Expanded( - child: _buildStatItem( - context, - icon: Icons.book, - title: 'Active Courses', - value: '9', - color: Colors.green, + Container( + width: 1, + height: 50, + color: Colors.grey.withValues(alpha: 0.3), ), - ), - Container(width: 1, height: 50, color: Colors.grey.withOpacity(0.3)), - Expanded( - child: _buildStatItem( - context, - icon: Icons.grade, - title: 'CGPA', - value: '3.36', - color: Colors.orange, + Expanded( + child: _buildStatItem( + context, + icon: Icons.book, + title: 'Courses', + value: + isLoading + ? '--' + : (latestResult?.courses.length ?? 0).toString(), + color: Colors.green, + ), ), - ), - ], - ), + Container( + width: 1, + height: 50, + color: Colors.grey.withValues(alpha: 0.3), + ), + Expanded( + child: _buildStatItem( + context, + icon: Icons.grade, + title: 'CGPA', + value: + isLoading + ? '--' + : resultController.overallGPA.value.toStringAsFixed(2), + color: Colors.orange, + ), + ), + ], + ); + }), ); } + AcademicResultModel? _latestResult(List results) { + if (results.isEmpty) return null; + final sorted = [...results]; + sorted.sort((a, b) { + final yearCompare = a.academicYear.compareTo(b.academicYear); + if (yearCompare != 0) return yearCompare; + return _semesterOrder(a.semester).compareTo(_semesterOrder(b.semester)); + }); + return sorted.last; + } + + int _semesterOrder(String semester) { + final match = RegExp(r'\d+').firstMatch(semester); + return int.tryParse(match?.group(0) ?? '') ?? 0; + } + Widget _buildStatItem( BuildContext context, { required IconData icon, @@ -662,7 +712,7 @@ class DashboardView extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: color.withOpacity(0.1), + color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: color, size: 24), @@ -682,7 +732,7 @@ class DashboardView extends StatelessWidget { fontSize: 12, color: Theme.of( context, - ).textTheme.bodyMedium?.color?.withOpacity(0.7), + ).textTheme.bodyMedium?.color?.withValues(alpha: 0.7), ), textAlign: TextAlign.center, ), @@ -784,7 +834,7 @@ class DashboardView extends StatelessWidget { borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: gradient[0].withOpacity(0.3), + color: gradient[0].withValues(alpha: 0.3), blurRadius: 10, offset: const Offset(0, 5), ), @@ -798,7 +848,7 @@ class DashboardView extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.2), + color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: Colors.white, size: 28), @@ -816,7 +866,7 @@ class DashboardView extends StatelessWidget { Text( subtitle, style: TextStyle( - color: Colors.white.withOpacity(0.8), + color: Colors.white.withValues(alpha: 0.8), fontSize: 12, ), ), @@ -919,7 +969,7 @@ class DashboardView extends StatelessWidget { Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: color.withOpacity(0.1), + color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: color, size: 24), @@ -944,7 +994,7 @@ class DashboardView extends StatelessWidget { fontSize: 14, color: Theme.of( context, - ).textTheme.bodyMedium?.color?.withOpacity(0.7), + ).textTheme.bodyMedium?.color?.withValues(alpha: 0.7), ), ), ], @@ -955,7 +1005,7 @@ class DashboardView extends StatelessWidget { size: 16, color: Theme.of( context, - ).textTheme.bodyMedium?.color?.withOpacity(0.5), + ).textTheme.bodyMedium?.color?.withValues(alpha: 0.5), ), ], ), @@ -973,7 +1023,7 @@ class DashboardView extends StatelessWidget { borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 5), ), @@ -1025,7 +1075,7 @@ class DashboardView extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: color.withOpacity(0.1), + color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 20), @@ -1050,7 +1100,7 @@ class DashboardView extends StatelessWidget { fontSize: 12, color: Theme.of( context, - ).textTheme.bodyMedium?.color?.withOpacity(0.7), + ).textTheme.bodyMedium?.color?.withValues(alpha: 0.7), ), ), ], @@ -1062,7 +1112,7 @@ class DashboardView extends StatelessWidget { fontSize: 12, color: Theme.of( context, - ).textTheme.bodyMedium?.color?.withOpacity(0.5), + ).textTheme.bodyMedium?.color?.withValues(alpha: 0.5), ), ), ], @@ -1082,7 +1132,7 @@ class DashboardView extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), + color: Colors.red.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.logout, color: Colors.red, size: 24), diff --git a/lib/views/id_card_view.dart b/lib/views/id_card_view.dart index b82b89f..5dd8d73 100644 --- a/lib/views/id_card_view.dart +++ b/lib/views/id_card_view.dart @@ -1,428 +1,428 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:flip_card/flip_card.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:qr_flutter/qr_flutter.dart'; -import '../controllers/auth_controller.dart'; - -class IDCardView extends StatelessWidget { - const IDCardView({super.key}); - - @override - Widget build(BuildContext context) { - final authController = Get.find(); - final user = authController.user; - - return Scaffold( - appBar: AppBar( - title: const Text('Student ID Card'), - backgroundColor: Colors.transparent, - elevation: 0, - ), - extendBodyBehindAppBar: true, - body: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.blue.shade100, Colors.purple.shade100], - ), - ), - child: Center( - child: Padding( - padding: const EdgeInsets.all(20.0), - child: FlipCard( - direction: FlipDirection.HORIZONTAL, - speed: 1000, - onFlipDone: (status) { - print(status); - }, - front: _buildFrontCard(context, user), - back: _buildBackCard(context, user), - ), - ), - ), - ), - ); - } - - Widget _buildFrontCard(BuildContext context, user) { - return Container( - width: 300, - height: 470, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.blue.shade800, - Colors.blue.shade600, - Colors.blue.shade400, - ], - ), - boxShadow: [ - BoxShadow( - color: Colors.blue.withOpacity(0.3), - blurRadius: 15, - offset: const Offset(0, 8), - ), - ], - ), - child: Stack( - children: [ - // Background pattern - Positioned( - right: -30, - top: -30, - child: Container( - width: 120, - height: 120, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.1), - ), - ), - ), - Positioned( - left: -40, - bottom: 100, - child: Container( - width: 100, - height: 100, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.05), - ), - ), - ), - // Content - Padding( - padding: const EdgeInsets.all(20), - child: Column( - children: [ - // University header - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 15), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Column( - children: [ - Text( - 'PSTU', - style: TextStyle( - color: Colors.white, - fontSize: 28, - fontWeight: FontWeight.bold, - letterSpacing: 3, - ), - ), - Text( - 'STUDENT ID CARD', - style: TextStyle( - color: Colors.white.withOpacity(0.9), - fontSize: 12, - letterSpacing: 2, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - const SizedBox(height: 25), - - // Profile image - Container( - width: 120, - height: 120, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 3), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.3), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: ClipOval( - child: - user?.profileImageUrl != null - ? CachedNetworkImage( - imageUrl: user!.profileImageUrl!, - fit: BoxFit.cover, - placeholder: - (context, url) => Container( - color: Colors.white, - child: const Icon( - Icons.person, - color: Colors.grey, - size: 60, - ), - ), - errorWidget: - (context, url, error) => Container( - color: Colors.white, - child: const Icon( - Icons.person, - color: Colors.grey, - size: 60, - ), - ), - ) - : Container( - color: Colors.white, - child: const Icon( - Icons.person, - color: Colors.grey, - size: 60, - ), - ), - ), - ), - const SizedBox(height: 20), - - // Student details - Container( - width: double.infinity, - padding: const EdgeInsets.all(15), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Column( - children: [ - Text( - user?.name ?? 'Name not available', - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 8), - _buildVerticalInfoRow('Student ID', user?.id ?? 'N/A'), - const SizedBox(height: 6), - _buildVerticalInfoRow( - 'Registration', - user?.regi ?? 'N/A', - ), - const SizedBox(height: 6), - _buildVerticalInfoRow('Faculty', user?.faculty ?? 'N/A'), - ], - ), - ), - const Spacer(), - - // Footer - Text( - 'Tap to see QR Code', - style: TextStyle( - color: Colors.white.withOpacity(0.7), - fontSize: 11, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildBackCard(BuildContext context, user) { - // Generate QR code data - String qrData = ''' -Name: ${user?.name ?? 'N/A'} -ID: ${user?.id ?? 'N/A'} -Registration: ${user?.regi ?? 'N/A'} -Faculty: ${user?.faculty ?? 'N/A'} -Email: ${user?.email ?? 'N/A'} -Phone: ${user?.phone ?? 'N/A'} -'''; - - return Container( - width: 270, - height: 420, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.grey.shade800, - Colors.grey.shade700, - Colors.grey.shade600, - ], - ), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.3), - blurRadius: 15, - offset: const Offset(0, 8), - ), - ], - ), - child: Stack( - children: [ - // Background pattern - Positioned( - left: -30, - top: -30, - child: Container( - width: 120, - height: 120, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withOpacity(0.1), - ), - ), - ), - // Content - Padding( - padding: const EdgeInsets.all(20), - child: Column( - children: [ - // University header - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Column( - children: [ - Text( - 'Patuakhali Science and Technology University', - style: TextStyle( - color: Colors.white, - fontSize: 13, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - Text( - 'Dumki, Patuakhali', - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontSize: 11, - ), - textAlign: TextAlign.center, - ), - ], - ), - ), - const SizedBox(height: 20), - - // QR Code - Container( - width: 140, - height: 140, - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.2), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ], - ), - child: QrImageView( - data: qrData, - version: QrVersions.auto, - size: 120.0, - backgroundColor: Colors.white, - ), - ), - const SizedBox(height: 20), - - // Student information - Container( - width: double.infinity, - padding: const EdgeInsets.all(15), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(10), - ), - child: Column( - children: [ - _buildVerticalInfoRow('Email', user?.email ?? 'N/A'), - const SizedBox(height: 8), - _buildVerticalInfoRow('Phone', user?.phone ?? 'N/A'), - const SizedBox(height: 8), - _buildVerticalInfoRow('Session', '2023-24'), - ], - ), - ), - const Spacer(), - - // Footer - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Valid Until: Dec 2027', - style: TextStyle( - color: Colors.white.withOpacity(0.7), - fontSize: 10, - ), - ), - Text( - 'Tap to flip', - style: TextStyle( - color: Colors.white.withOpacity(0.7), - fontSize: 10, - fontStyle: FontStyle.italic, - ), - ), - ], - ), - ], - ), - ), - ], - ), - ); - } - - Widget _buildVerticalInfoRow(String label, String value) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label + ':', - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ), - Expanded( - child: Text( - value, - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.right, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ); - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flip_card/flip_card.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import '../controllers/auth_controller.dart'; + +class IDCardView extends StatelessWidget { + const IDCardView({super.key}); + + @override + Widget build(BuildContext context) { + final authController = Get.find(); + final user = authController.user; + + return Scaffold( + appBar: AppBar( + title: const Text('Student ID Card'), + backgroundColor: Colors.transparent, + elevation: 0, + ), + extendBodyBehindAppBar: true, + body: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.blue.shade100, Colors.purple.shade100], + ), + ), + child: Center( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: FlipCard( + direction: FlipDirection.HORIZONTAL, + speed: 1000, + onFlipDone: (status) { + print(status); + }, + front: _buildFrontCard(context, user), + back: _buildBackCard(context, user), + ), + ), + ), + ), + ); + } + + Widget _buildFrontCard(BuildContext context, user) { + return Container( + width: 300, + height: 470, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.blue.shade800, + Colors.blue.shade600, + Colors.blue.shade400, + ], + ), + boxShadow: [ + BoxShadow( + color: Colors.blue.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 8), + ), + ], + ), + child: Stack( + children: [ + // Background pattern + Positioned( + right: -30, + top: -30, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.1), + ), + ), + ), + Positioned( + left: -40, + bottom: 100, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.05), + ), + ), + ), + // Content + Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + // University header + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 15), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + Text( + 'PSTU', + style: TextStyle( + color: Colors.white, + fontSize: 28, + fontWeight: FontWeight.bold, + letterSpacing: 3, + ), + ), + Text( + 'STUDENT ID CARD', + style: TextStyle( + color: Colors.white.withOpacity(0.9), + fontSize: 12, + letterSpacing: 2, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + const SizedBox(height: 25), + + // Profile image + Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 3), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 10, + offset: const Offset(0, 5), + ), + ], + ), + child: ClipOval( + child: + user?.profileImageUrl != null + ? CachedNetworkImage( + imageUrl: user!.profileImageUrl!, + fit: BoxFit.cover, + placeholder: + (context, url) => Container( + color: Colors.white, + child: const Icon( + Icons.person, + color: Colors.grey, + size: 60, + ), + ), + errorWidget: + (context, url, error) => Container( + color: Colors.white, + child: const Icon( + Icons.person, + color: Colors.grey, + size: 60, + ), + ), + ) + : Container( + color: Colors.white, + child: const Icon( + Icons.person, + color: Colors.grey, + size: 60, + ), + ), + ), + ), + const SizedBox(height: 20), + + // Student details + Container( + width: double.infinity, + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + Text( + user?.name ?? 'Name not available', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + _buildVerticalInfoRow('Student ID', user?.id ?? 'N/A'), + const SizedBox(height: 6), + _buildVerticalInfoRow( + 'Registration', + user?.regi ?? 'N/A', + ), + const SizedBox(height: 6), + _buildVerticalInfoRow('Faculty', user?.faculty ?? 'N/A'), + ], + ), + ), + const Spacer(), + + // Footer + Text( + 'Tap to see QR Code', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 11, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildBackCard(BuildContext context, user) { + // Generate QR code data + String qrData = ''' +Name: ${user?.name ?? 'N/A'} +ID: ${user?.id ?? 'N/A'} +Registration: ${user?.regi ?? 'N/A'} +Faculty: ${user?.faculty ?? 'N/A'} +Email: ${user?.email ?? 'N/A'} +Phone: ${user?.phone ?? 'N/A'} +'''; + + return Container( + width: 270, + height: 420, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.grey.shade800, + Colors.grey.shade700, + Colors.grey.shade600, + ], + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 8), + ), + ], + ), + child: Stack( + children: [ + // Background pattern + Positioned( + left: -30, + top: -30, + child: Container( + width: 120, + height: 120, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withOpacity(0.1), + ), + ), + ), + // Content + Padding( + padding: const EdgeInsets.all(20), + child: Column( + children: [ + // University header + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + Text( + 'Patuakhali Science and Technology University', + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + Text( + 'Dumki, Patuakhali', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 11, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + const SizedBox(height: 20), + + // QR Code + Container( + width: 140, + height: 140, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: QrImageView( + data: qrData, + version: QrVersions.auto, + size: 120.0, + backgroundColor: Colors.white, + ), + ), + const SizedBox(height: 20), + + // Student information + Container( + width: double.infinity, + padding: const EdgeInsets.all(15), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.1), + borderRadius: BorderRadius.circular(10), + ), + child: Column( + children: [ + _buildVerticalInfoRow('Email', user?.email ?? 'N/A'), + const SizedBox(height: 8), + _buildVerticalInfoRow('Phone', user?.phone ?? 'N/A'), + const SizedBox(height: 8), + _buildVerticalInfoRow('Session', '2023-24'), + ], + ), + ), + const Spacer(), + + // Footer + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Valid Until: Dec 2027', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 10, + ), + ), + Text( + 'Tap to flip', + style: TextStyle( + color: Colors.white.withOpacity(0.7), + fontSize: 10, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildVerticalInfoRow(String label, String value) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label + ':', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + Expanded( + child: Text( + value, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.right, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + } +} diff --git a/lib/views/notice_screen.dart b/lib/views/notice_screen.dart index 1ab53a2..85cb32a 100644 --- a/lib/views/notice_screen.dart +++ b/lib/views/notice_screen.dart @@ -1,223 +1,223 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../controllers/notice_controller.dart'; - -class NoticeScreen extends StatelessWidget { - const NoticeScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final NoticeController noticeController = Get.put(NoticeController()); - - return Scaffold( - backgroundColor: Colors.grey[50], - appBar: AppBar( - title: Text( - 'Notices', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), - ), - backgroundColor: Colors.transparent, - elevation: 0, - flexibleSpace: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue[600]!, Colors.blue[400]!], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - ), - ), - ), - body: Obx(() { - if (noticeController.notices.isEmpty) { - return _buildEmptyState(); - } - - return RefreshIndicator( - onRefresh: () async { - // Remove await if fetchNotices returns void - noticeController.fetchNotices(); - }, - color: Colors.blue[600], - child: ListView.builder( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), - itemCount: noticeController.notices.length, - itemBuilder: (context, index) { - final notice = noticeController.notices[index]; - return TweenAnimationBuilder( - duration: Duration(milliseconds: 300 + (index * 100)), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return Transform.translate( - offset: Offset(0, (1 - value) * 50), - child: Opacity( - opacity: value, - child: _buildNoticeCard(notice, index), - ), - ); - }, - ); - }, - ), - ); - }), - ); - } - - Widget _buildNoticeCard(notice, int index) { - return Container( - margin: EdgeInsets.only(bottom: 16), - child: Card( - elevation: 8, - shadowColor: Colors.blue.withOpacity(0.2), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - gradient: LinearGradient( - colors: [Colors.white, Colors.blue[50]!], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - ), - child: InkWell( - borderRadius: BorderRadius.circular(16), - onTap: () { - // Add navigation to notice details if needed - }, - child: Padding( - padding: EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.blue[100], - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.notifications_active, - color: Colors.blue[600], - size: 20, - ), - ), - SizedBox(width: 12), - Expanded( - child: Text( - notice.title, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.grey[800], - ), - ), - ), - Container( - padding: EdgeInsets.symmetric( - horizontal: 8, - vertical: 4, - ), - decoration: BoxDecoration( - color: Colors.blue[100], - borderRadius: BorderRadius.circular(12), - ), - child: Text( - _formatDate(notice.createdAt), - style: TextStyle( - fontSize: 11, - color: Colors.blue[700], - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - SizedBox(height: 16), - Container( - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey[200]!), - ), - child: Text( - notice.content, - style: TextStyle( - fontSize: 15, - color: Colors.grey[700], - height: 1.5, - ), - ), - ), - ], - ), - ), - ), - ), - ), - ); - } - - Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TweenAnimationBuilder( - duration: Duration(milliseconds: 800), - tween: Tween(begin: 0.0, end: 1.0), - builder: (context, value, child) { - return Transform.scale( - scale: value, - child: Container( - padding: EdgeInsets.all(24), - decoration: BoxDecoration( - color: Colors.grey[100], - shape: BoxShape.circle, - ), - child: Icon( - Icons.notifications_none, - size: 64, - color: Colors.grey[400], - ), - ), - ); - }, - ), - SizedBox(height: 24), - Text( - 'No notices available', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w600, - color: Colors.grey[600], - ), - ), - SizedBox(height: 8), - Text( - 'Check back later for updates', - style: TextStyle(fontSize: 14, color: Colors.grey[500]), - ), - ], - ), - ); - } - - String _formatDate(DateTime date) { - final now = DateTime.now(); - final difference = now.difference(date); - - if (difference.inDays == 0) { - return 'Today'; - } else if (difference.inDays == 1) { - return 'Yesterday'; - } else if (difference.inDays < 7) { - return '${difference.inDays}d ago'; - } else { - return '${date.day}/${date.month}/${date.year}'; - } - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controllers/notice_controller.dart'; + +class NoticeScreen extends StatelessWidget { + const NoticeScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final NoticeController noticeController = Get.put(NoticeController()); + + return Scaffold( + backgroundColor: Colors.grey[50], + appBar: AppBar( + title: Text( + 'Notices', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), + ), + backgroundColor: Colors.transparent, + elevation: 0, + flexibleSpace: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.blue[600]!, Colors.blue[400]!], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + ), + ), + body: Obx(() { + if (noticeController.notices.isEmpty) { + return _buildEmptyState(); + } + + return RefreshIndicator( + onRefresh: () async { + // Remove await if fetchNotices returns void + noticeController.fetchNotices(); + }, + color: Colors.blue[600], + child: ListView.builder( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), + itemCount: noticeController.notices.length, + itemBuilder: (context, index) { + final notice = noticeController.notices[index]; + return TweenAnimationBuilder( + duration: Duration(milliseconds: 300 + (index * 100)), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Transform.translate( + offset: Offset(0, (1 - value) * 50), + child: Opacity( + opacity: value, + child: _buildNoticeCard(notice, index), + ), + ); + }, + ); + }, + ), + ); + }), + ); + } + + Widget _buildNoticeCard(notice, int index) { + return Container( + margin: EdgeInsets.only(bottom: 16), + child: Card( + elevation: 8, + shadowColor: Colors.blue.withOpacity(0.2), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + gradient: LinearGradient( + colors: [Colors.white, Colors.blue[50]!], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: () { + // Add navigation to notice details if needed + }, + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.blue[100], + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.notifications_active, + color: Colors.blue[600], + size: 20, + ), + ), + SizedBox(width: 12), + Expanded( + child: Text( + notice.title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.grey[800], + ), + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.blue[100], + borderRadius: BorderRadius.circular(12), + ), + child: Text( + _formatDate(notice.createdAt), + style: TextStyle( + fontSize: 11, + color: Colors.blue[700], + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + SizedBox(height: 16), + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey[200]!), + ), + child: Text( + notice.content, + style: TextStyle( + fontSize: 15, + color: Colors.grey[700], + height: 1.5, + ), + ), + ), + ], + ), + ), + ), + ), + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TweenAnimationBuilder( + duration: Duration(milliseconds: 800), + tween: Tween(begin: 0.0, end: 1.0), + builder: (context, value, child) { + return Transform.scale( + scale: value, + child: Container( + padding: EdgeInsets.all(24), + decoration: BoxDecoration( + color: Colors.grey[100], + shape: BoxShape.circle, + ), + child: Icon( + Icons.notifications_none, + size: 64, + color: Colors.grey[400], + ), + ), + ); + }, + ), + SizedBox(height: 24), + Text( + 'No notices available', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + color: Colors.grey[600], + ), + ), + SizedBox(height: 8), + Text( + 'Check back later for updates', + style: TextStyle(fontSize: 14, color: Colors.grey[500]), + ), + ], + ), + ); + } + + String _formatDate(DateTime date) { + final now = DateTime.now(); + final difference = now.difference(date); + + if (difference.inDays == 0) { + return 'Today'; + } else if (difference.inDays == 1) { + return 'Yesterday'; + } else if (difference.inDays < 7) { + return '${difference.inDays}d ago'; + } else { + return '${date.day}/${date.month}/${date.year}'; + } + } +} diff --git a/lib/views/notification_history_view.dart b/lib/views/notification_history_view.dart index 20513a1..9f61d08 100644 --- a/lib/views/notification_history_view.dart +++ b/lib/views/notification_history_view.dart @@ -1,350 +1,350 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:myedu/controllers/notification_controller.dart'; - -class NotificationHistoryView extends StatelessWidget { - const NotificationHistoryView({super.key}); - - @override - Widget build(BuildContext context) { - final NotificationController notificationController = - Get.find(); - - return Scaffold( - appBar: AppBar( - title: const Text('Notifications'), - backgroundColor: const Color(0xFF6366F1), - foregroundColor: Colors.white, - elevation: 0, - actions: [ - IconButton( - onPressed: - () => _showClearAllDialog(context, notificationController), - icon: const Icon(Icons.clear_all), - tooltip: 'Clear All', - ), - IconButton( - onPressed: () => notificationController.markAllAsRead(), - icon: const Icon(Icons.mark_email_read), - tooltip: 'Mark All as Read', - ), - ], - ), - body: Obx(() { - if (notificationController.notifications.isEmpty) { - return _buildEmptyState(); - } - - return RefreshIndicator( - onRefresh: () async { - // Refresh logic if needed - }, - child: ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: notificationController.notifications.length, - itemBuilder: (context, index) { - final notification = notificationController.notifications[index]; - return _buildNotificationCard( - context, - notification, - notificationController, - ); - }, - ), - ); - }), - floatingActionButton: FloatingActionButton( - onPressed: () => notificationController.sendTestNotification(), - backgroundColor: const Color(0xFF6366F1), - child: const Icon(Icons.notification_add, color: Colors.white), - tooltip: 'Send Test Notification', - ), - ); - } - - Widget _buildEmptyState() { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: const Color(0xFF6366F1).withOpacity(0.1), - shape: BoxShape.circle, - ), - child: const Icon( - Icons.notifications_none, - size: 64, - color: Color(0xFF6366F1), - ), - ), - const SizedBox(height: 24), - Text( - 'No notifications yet', - style: Get.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.bold, - color: Colors.grey[600], - ), - ), - const SizedBox(height: 8), - Text( - 'You\'ll see your notifications here when you receive them', - style: Get.textTheme.bodyMedium?.copyWith(color: Colors.grey[500]), - textAlign: TextAlign.center, - ), - ], - ), - ); - } - - Widget _buildNotificationCard( - BuildContext context, - Map notification, - NotificationController controller, - ) { - final bool isRead = notification['isRead'] ?? false; - final DateTime timestamp = DateTime.parse(notification['timestamp']); - final String timeAgo = _getTimeAgo(timestamp); - - return Card( - margin: const EdgeInsets.only(bottom: 12), - elevation: isRead ? 1 : 3, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: - isRead - ? Colors.transparent - : const Color(0xFF6366F1).withOpacity(0.3), - width: isRead ? 0 : 1, - ), - ), - child: InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { - controller.markAsRead(notification['id']); - _handleNotificationTap(notification); - }, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Notification icon - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: const Color(0xFF6366F1).withOpacity(0.1), - borderRadius: BorderRadius.circular(24), - ), - child: Icon( - _getNotificationIcon(notification['data']), - color: const Color(0xFF6366F1), - size: 24, - ), - ), - const SizedBox(width: 12), - - // Notification content - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - notification['title'] ?? 'Notification', - style: Get.textTheme.titleMedium?.copyWith( - fontWeight: - isRead ? FontWeight.normal : FontWeight.bold, - color: isRead ? Colors.grey[600] : Colors.black87, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - if (!isRead) - Container( - width: 8, - height: 8, - decoration: const BoxDecoration( - color: Color(0xFF6366F1), - shape: BoxShape.circle, - ), - ), - ], - ), - const SizedBox(height: 4), - Text( - notification['body'] ?? '', - style: Get.textTheme.bodyMedium?.copyWith( - color: Colors.grey[600], - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 8), - Row( - children: [ - Icon( - Icons.access_time, - size: 14, - color: Colors.grey[500], - ), - const SizedBox(width: 4), - Text( - timeAgo, - style: Get.textTheme.bodySmall?.copyWith( - color: Colors.grey[500], - ), - ), - const Spacer(), - IconButton( - onPressed: - () => _showDeleteDialog( - context, - notification['id'], - controller, - ), - icon: Icon( - Icons.delete_outline, - size: 18, - color: Colors.grey[500], - ), - visualDensity: VisualDensity.compact, - ), - ], - ), - ], - ), - ), - ], - ), - ), - ), - ); - } - - IconData _getNotificationIcon(Map? data) { - if (data == null) return Icons.notifications; - - String screen = data['screen']?.toString().toLowerCase() ?? ''; - switch (screen) { - case 'notices': - return Icons.campaign; - case 'fees': - return Icons.payment; - case 'grades': - return Icons.grade; - case 'schedule': - return Icons.schedule; - default: - return Icons.notifications; - } - } - - String _getTimeAgo(DateTime timestamp) { - final now = DateTime.now(); - final difference = now.difference(timestamp); - - if (difference.inDays > 0) { - return '${difference.inDays}d ago'; - } else if (difference.inHours > 0) { - return '${difference.inHours}h ago'; - } else if (difference.inMinutes > 0) { - return '${difference.inMinutes}m ago'; - } else { - return 'Just now'; - } - } - - void _handleNotificationTap(Map notification) { - final data = notification['data'] as Map?; - if (data != null) { - String? screen = data['screen']?.toString(); - String? route = data['route']?.toString(); - - if (route != null) { - Get.toNamed(route); - } else if (screen != null) { - _navigateBasedOnScreen(screen); - } - } - } - - void _navigateBasedOnScreen(String screen) { - switch (screen.toLowerCase()) { - case 'notices': - Get.toNamed('/notices'); - break; - case 'dashboard': - Get.toNamed('/dashboard'); - break; - default: - Get.toNamed('/dashboard'); - break; - } - } - - void _showDeleteDialog( - BuildContext context, - int notificationId, - NotificationController controller, - ) { - showDialog( - context: context, - builder: - (context) => AlertDialog( - title: const Text('Delete Notification'), - content: const Text( - 'Are you sure you want to delete this notification?', - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - controller.clearNotification(notificationId); - Navigator.of(context).pop(); - }, - child: const Text('Delete'), - ), - ], - ), - ); - } - - void _showClearAllDialog( - BuildContext context, - NotificationController controller, - ) { - showDialog( - context: context, - builder: - (context) => AlertDialog( - title: const Text('Clear All Notifications'), - content: const Text( - 'Are you sure you want to clear all notifications?', - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () { - controller.clearAllNotifications(); - Navigator.of(context).pop(); - }, - child: const Text('Clear All'), - ), - ], - ), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:myedu/controllers/notification_controller.dart'; + +class NotificationHistoryView extends StatelessWidget { + const NotificationHistoryView({super.key}); + + @override + Widget build(BuildContext context) { + final NotificationController notificationController = + Get.find(); + + return Scaffold( + appBar: AppBar( + title: const Text('Notifications'), + backgroundColor: const Color(0xFF6366F1), + foregroundColor: Colors.white, + elevation: 0, + actions: [ + IconButton( + onPressed: + () => _showClearAllDialog(context, notificationController), + icon: const Icon(Icons.clear_all), + tooltip: 'Clear All', + ), + IconButton( + onPressed: () => notificationController.markAllAsRead(), + icon: const Icon(Icons.mark_email_read), + tooltip: 'Mark All as Read', + ), + ], + ), + body: Obx(() { + if (notificationController.notifications.isEmpty) { + return _buildEmptyState(); + } + + return RefreshIndicator( + onRefresh: () async { + // Refresh logic if needed + }, + child: ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: notificationController.notifications.length, + itemBuilder: (context, index) { + final notification = notificationController.notifications[index]; + return _buildNotificationCard( + context, + notification, + notificationController, + ); + }, + ), + ); + }), + floatingActionButton: FloatingActionButton( + onPressed: () => notificationController.sendTestNotification(), + backgroundColor: const Color(0xFF6366F1), + child: const Icon(Icons.notification_add, color: Colors.white), + tooltip: 'Send Test Notification', + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + color: const Color(0xFF6366F1).withOpacity(0.1), + shape: BoxShape.circle, + ), + child: const Icon( + Icons.notifications_none, + size: 64, + color: Color(0xFF6366F1), + ), + ), + const SizedBox(height: 24), + Text( + 'No notifications yet', + style: Get.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.bold, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 8), + Text( + 'You\'ll see your notifications here when you receive them', + style: Get.textTheme.bodyMedium?.copyWith(color: Colors.grey[500]), + textAlign: TextAlign.center, + ), + ], + ), + ); + } + + Widget _buildNotificationCard( + BuildContext context, + Map notification, + NotificationController controller, + ) { + final bool isRead = notification['isRead'] ?? false; + final DateTime timestamp = DateTime.parse(notification['timestamp']); + final String timeAgo = _getTimeAgo(timestamp); + + return Card( + margin: const EdgeInsets.only(bottom: 12), + elevation: isRead ? 1 : 3, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: + isRead + ? Colors.transparent + : const Color(0xFF6366F1).withOpacity(0.3), + width: isRead ? 0 : 1, + ), + ), + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: () { + controller.markAsRead(notification['id']); + _handleNotificationTap(notification); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Notification icon + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: const Color(0xFF6366F1).withOpacity(0.1), + borderRadius: BorderRadius.circular(24), + ), + child: Icon( + _getNotificationIcon(notification['data']), + color: const Color(0xFF6366F1), + size: 24, + ), + ), + const SizedBox(width: 12), + + // Notification content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + notification['title'] ?? 'Notification', + style: Get.textTheme.titleMedium?.copyWith( + fontWeight: + isRead ? FontWeight.normal : FontWeight.bold, + color: isRead ? Colors.grey[600] : Colors.black87, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + if (!isRead) + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Color(0xFF6366F1), + shape: BoxShape.circle, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + notification['body'] ?? '', + style: Get.textTheme.bodyMedium?.copyWith( + color: Colors.grey[600], + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.access_time, + size: 14, + color: Colors.grey[500], + ), + const SizedBox(width: 4), + Text( + timeAgo, + style: Get.textTheme.bodySmall?.copyWith( + color: Colors.grey[500], + ), + ), + const Spacer(), + IconButton( + onPressed: + () => _showDeleteDialog( + context, + notification['id'], + controller, + ), + icon: Icon( + Icons.delete_outline, + size: 18, + color: Colors.grey[500], + ), + visualDensity: VisualDensity.compact, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ); + } + + IconData _getNotificationIcon(Map? data) { + if (data == null) return Icons.notifications; + + String screen = data['screen']?.toString().toLowerCase() ?? ''; + switch (screen) { + case 'notices': + return Icons.campaign; + case 'fees': + return Icons.payment; + case 'grades': + return Icons.grade; + case 'schedule': + return Icons.schedule; + default: + return Icons.notifications; + } + } + + String _getTimeAgo(DateTime timestamp) { + final now = DateTime.now(); + final difference = now.difference(timestamp); + + if (difference.inDays > 0) { + return '${difference.inDays}d ago'; + } else if (difference.inHours > 0) { + return '${difference.inHours}h ago'; + } else if (difference.inMinutes > 0) { + return '${difference.inMinutes}m ago'; + } else { + return 'Just now'; + } + } + + void _handleNotificationTap(Map notification) { + final data = notification['data'] as Map?; + if (data != null) { + String? screen = data['screen']?.toString(); + String? route = data['route']?.toString(); + + if (route != null) { + Get.toNamed(route); + } else if (screen != null) { + _navigateBasedOnScreen(screen); + } + } + } + + void _navigateBasedOnScreen(String screen) { + switch (screen.toLowerCase()) { + case 'notices': + Get.toNamed('/notices'); + break; + case 'dashboard': + Get.toNamed('/dashboard'); + break; + default: + Get.toNamed('/dashboard'); + break; + } + } + + void _showDeleteDialog( + BuildContext context, + int notificationId, + NotificationController controller, + ) { + showDialog( + context: context, + builder: + (context) => AlertDialog( + title: const Text('Delete Notification'), + content: const Text( + 'Are you sure you want to delete this notification?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + controller.clearNotification(notificationId); + Navigator.of(context).pop(); + }, + child: const Text('Delete'), + ), + ], + ), + ); + } + + void _showClearAllDialog( + BuildContext context, + NotificationController controller, + ) { + showDialog( + context: context, + builder: + (context) => AlertDialog( + title: const Text('Clear All Notifications'), + content: const Text( + 'Are you sure you want to clear all notifications?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + controller.clearAllNotifications(); + Navigator.of(context).pop(); + }, + child: const Text('Clear All'), + ), + ], + ), + ); + } +} diff --git a/lib/views/otp_verification_view.dart b/lib/views/otp_verification_view.dart index a46d1e8..21df258 100644 --- a/lib/views/otp_verification_view.dart +++ b/lib/views/otp_verification_view.dart @@ -1,403 +1,403 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:myedu/components/custom_button.dart'; -import 'package:myedu/components/custom_text_field.dart'; -import 'package:myedu/constraints/app_constants.dart'; -import 'package:myedu/controllers/auth_controller.dart'; -import 'package:myedu/controllers/theme_controller.dart'; - -class OtpVerificationView extends StatefulWidget { - final String email; - final String name; - final String faculty; - final String id; - final String regi; - final String phone; - final String password; - - const OtpVerificationView({ - super.key, - required this.email, - required this.name, - required this.faculty, - required this.id, - required this.regi, - required this.phone, - required this.password, - }); - - @override - State createState() => _OtpVerificationViewState(); -} - -class _OtpVerificationViewState extends State - with TickerProviderStateMixin { - final _formKey = GlobalKey(); - final _otpController = TextEditingController(); - - late AnimationController _fadeController; - late AnimationController _slideController; - late Animation _fadeAnimation; - late Animation _slideAnimation; - - bool _isResending = false; - int _resendCountdown = 60; - bool _canResend = false; - - @override - void initState() { - super.initState(); - _setupAnimations(); - _startResendTimer(); - } - - void _setupAnimations() { - _fadeController = AnimationController( - duration: const Duration(milliseconds: 800), - vsync: this, - ); - _slideController = AnimationController( - duration: const Duration(milliseconds: 1000), - vsync: this, - ); - - _fadeAnimation = Tween( - begin: 0.0, - end: 1.0, - ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); - - _slideAnimation = Tween( - begin: const Offset(0, 0.3), - end: Offset.zero, - ).animate( - CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack), - ); - - _fadeController.forward(); - _slideController.forward(); - } - - void _startResendTimer() { - _resendCountdown = 60; - _canResend = false; - setState(() {}); - - Future.doWhile(() async { - await Future.delayed(const Duration(seconds: 1)); - setState(() { - _resendCountdown--; - }); - return _resendCountdown > 0; - }).then((_) { - setState(() { - _canResend = true; - }); - }); - } - - @override - void dispose() { - _fadeController.dispose(); - _slideController.dispose(); - _otpController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Verify Email'), - actions: [ - GetBuilder( - builder: (controller) { - return IconButton( - icon: Icon( - controller.isDarkMode ? Icons.light_mode : Icons.dark_mode, - ), - onPressed: controller.toggleTheme, - ); - }, - ), - ], - ), - body: SafeArea( - child: GetBuilder( - builder: (controller) { - return SingleChildScrollView( - padding: const EdgeInsets.all(AppConstants.mediumSpacing), - child: FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildHeader(), - const SizedBox(height: AppConstants.largeSpacing), - _buildOtpForm(controller), - const SizedBox(height: AppConstants.largeSpacing), - _buildResendSection(controller), - ], - ), - ), - ), - ); - }, - ), - ), - ); - } - - Widget _buildHeader() { - return Column( - children: [ - Container( - height: 80, - width: 80, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.email_outlined, - size: 40, - color: Theme.of(context).primaryColor, - ), - ), - const SizedBox(height: AppConstants.mediumSpacing), - Text( - 'Verify Your Email', - style: Theme.of( - context, - ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w600), - ), - const SizedBox(height: AppConstants.smallSpacing), - Text( - 'We\'ve sent a verification code to', - style: Theme.of( - context, - ).textTheme.bodyLarge?.copyWith(color: Colors.grey[600]), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppConstants.smallSpacing), - Text( - widget.email, - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppConstants.mediumSpacing), - Container( - padding: const EdgeInsets.all(AppConstants.mediumSpacing), - decoration: BoxDecoration( - color: Theme.of(context).primaryColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Theme.of(context).primaryColor.withOpacity(0.3), - ), - ), - child: Row( - children: [ - Icon( - Icons.info_outline, - color: Theme.of(context).primaryColor, - size: 20, - ), - const SizedBox(width: AppConstants.smallSpacing), - Expanded( - child: Text( - 'Enter the 6-digit code sent to your email to verify your account.', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).primaryColor, - ), - ), - ), - ], - ), - ), - ], - ); - } - - Widget _buildOtpForm(AuthController controller) { - return Card( - elevation: AppConstants.mediumElevation, - child: Padding( - padding: const EdgeInsets.all(AppConstants.largeSpacing), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'Enter Verification Code', - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppConstants.largeSpacing), - - // OTP Input Field - CustomTextField( - controller: _otpController, - label: 'OTP Code', - hint: 'Enter 6-digit code', - prefixIcon: Icons.security_outlined, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - LengthLimitingTextInputFormatter(6), - ], - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter the OTP code'; - } - if (value.length != 6) { - return 'OTP code must be 6 digits'; - } - return null; - }, - ), - const SizedBox(height: AppConstants.largeSpacing), - - // Verify Button - GetBuilder( - builder: (controller) { - return CustomButton( - text: 'Verify & Create Account', - onPressed: () => _verifyOtp(controller), - isLoading: controller.isLoading, - icon: Icons.verified_outlined, - ); - }, - ), - ], - ), - ), - ), - ); - } - - Widget _buildResendSection(AuthController controller) { - return Column( - children: [ - Text( - 'Didn\'t receive the code?', - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith(color: Colors.grey[600]), - ), - const SizedBox(height: AppConstants.smallSpacing), - if (_canResend) - TextButton( - onPressed: _isResending ? null : () => _resendOtp(controller), - child: - _isResending - ? const SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : Text( - 'Resend Code', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w600, - ), - ), - ) - else - Text( - 'Resend code in $_resendCountdown seconds', - style: Theme.of( - context, - ).textTheme.bodyMedium?.copyWith(color: Colors.grey[500]), - ), - const SizedBox(height: AppConstants.largeSpacing), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Wrong email? ', - style: Theme.of(context).textTheme.bodyMedium, - ), - TextButton( - onPressed: () => Get.back(), - child: Text( - 'Go Back', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - ], - ); - } - - void _verifyOtp(AuthController controller) async { - if (_formKey.currentState!.validate()) { - final otp = _otpController.text.trim(); - - // Verify OTP with the controller - final success = await controller.verifyOtpAndSignUp( - otp: otp, - name: widget.name, - email: widget.email, - password: widget.password, - faculty: widget.faculty, - id: widget.id, - regi: widget.regi, - phone: widget.phone, - ); - - if (success) { - Get.offAllNamed(AppRoutes.dashboard); - } - } - } - - void _resendOtp(AuthController controller) async { - setState(() { - _isResending = true; - }); - - try { - await controller.sendOtpEmail(widget.email); - _startResendTimer(); - - Get.snackbar( - 'Success', - 'OTP code resent successfully!', - backgroundColor: Colors.green, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } catch (e) { - Get.snackbar( - 'Error', - 'Failed to resend OTP. Please try again.', - backgroundColor: Colors.red, - colorText: Colors.white, - snackPosition: SnackPosition.TOP, - duration: const Duration(seconds: 3), - margin: const EdgeInsets.all(16), - borderRadius: 12, - ); - } finally { - setState(() { - _isResending = false; - }); - } - } -} +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:myedu/components/custom_button.dart'; +import 'package:myedu/components/custom_text_field.dart'; +import 'package:myedu/constraints/app_constants.dart'; +import 'package:myedu/controllers/auth_controller.dart'; +import 'package:myedu/controllers/theme_controller.dart'; + +class OtpVerificationView extends StatefulWidget { + final String email; + final String name; + final String faculty; + final String id; + final String regi; + final String phone; + final String password; + + const OtpVerificationView({ + super.key, + required this.email, + required this.name, + required this.faculty, + required this.id, + required this.regi, + required this.phone, + required this.password, + }); + + @override + State createState() => _OtpVerificationViewState(); +} + +class _OtpVerificationViewState extends State + with TickerProviderStateMixin { + final _formKey = GlobalKey(); + final _otpController = TextEditingController(); + + late AnimationController _fadeController; + late AnimationController _slideController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + + bool _isResending = false; + int _resendCountdown = 60; + bool _canResend = false; + + @override + void initState() { + super.initState(); + _setupAnimations(); + _startResendTimer(); + } + + void _setupAnimations() { + _fadeController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + _slideController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); + + _slideAnimation = Tween( + begin: const Offset(0, 0.3), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack), + ); + + _fadeController.forward(); + _slideController.forward(); + } + + void _startResendTimer() { + _resendCountdown = 60; + _canResend = false; + setState(() {}); + + Future.doWhile(() async { + await Future.delayed(const Duration(seconds: 1)); + setState(() { + _resendCountdown--; + }); + return _resendCountdown > 0; + }).then((_) { + setState(() { + _canResend = true; + }); + }); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _otpController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Verify Email'), + actions: [ + GetBuilder( + builder: (controller) { + return IconButton( + icon: Icon( + controller.isDarkMode ? Icons.light_mode : Icons.dark_mode, + ), + onPressed: controller.toggleTheme, + ); + }, + ), + ], + ), + body: SafeArea( + child: GetBuilder( + builder: (controller) { + return SingleChildScrollView( + padding: const EdgeInsets.all(AppConstants.mediumSpacing), + child: FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeader(), + const SizedBox(height: AppConstants.largeSpacing), + _buildOtpForm(controller), + const SizedBox(height: AppConstants.largeSpacing), + _buildResendSection(controller), + ], + ), + ), + ), + ); + }, + ), + ), + ); + } + + Widget _buildHeader() { + return Column( + children: [ + Container( + height: 80, + width: 80, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.email_outlined, + size: 40, + color: Theme.of(context).primaryColor, + ), + ), + const SizedBox(height: AppConstants.mediumSpacing), + Text( + 'Verify Your Email', + style: Theme.of( + context, + ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: AppConstants.smallSpacing), + Text( + 'We\'ve sent a verification code to', + style: Theme.of( + context, + ).textTheme.bodyLarge?.copyWith(color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppConstants.smallSpacing), + Text( + widget.email, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryColor, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppConstants.mediumSpacing), + Container( + padding: const EdgeInsets.all(AppConstants.mediumSpacing), + decoration: BoxDecoration( + color: Theme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Theme.of(context).primaryColor.withOpacity(0.3), + ), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + color: Theme.of(context).primaryColor, + size: 20, + ), + const SizedBox(width: AppConstants.smallSpacing), + Expanded( + child: Text( + 'Enter the 6-digit code sent to your email to verify your account.', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).primaryColor, + ), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildOtpForm(AuthController controller) { + return Card( + elevation: AppConstants.mediumElevation, + child: Padding( + padding: const EdgeInsets.all(AppConstants.largeSpacing), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Enter Verification Code', + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppConstants.largeSpacing), + + // OTP Input Field + CustomTextField( + controller: _otpController, + label: 'OTP Code', + hint: 'Enter 6-digit code', + prefixIcon: Icons.security_outlined, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + LengthLimitingTextInputFormatter(6), + ], + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter the OTP code'; + } + if (value.length != 6) { + return 'OTP code must be 6 digits'; + } + return null; + }, + ), + const SizedBox(height: AppConstants.largeSpacing), + + // Verify Button + GetBuilder( + builder: (controller) { + return CustomButton( + text: 'Verify & Create Account', + onPressed: () => _verifyOtp(controller), + isLoading: controller.isLoading, + icon: Icons.verified_outlined, + ); + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildResendSection(AuthController controller) { + return Column( + children: [ + Text( + 'Didn\'t receive the code?', + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: Colors.grey[600]), + ), + const SizedBox(height: AppConstants.smallSpacing), + if (_canResend) + TextButton( + onPressed: _isResending ? null : () => _resendOtp(controller), + child: + _isResending + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Text( + 'Resend Code', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w600, + ), + ), + ) + else + Text( + 'Resend code in $_resendCountdown seconds', + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: Colors.grey[500]), + ), + const SizedBox(height: AppConstants.largeSpacing), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Wrong email? ', + style: Theme.of(context).textTheme.bodyMedium, + ), + TextButton( + onPressed: () => Get.back(), + child: Text( + 'Go Back', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ], + ); + } + + void _verifyOtp(AuthController controller) async { + if (_formKey.currentState!.validate()) { + final otp = _otpController.text.trim(); + + // Verify OTP with the controller + final success = await controller.verifyOtpAndSignUp( + otp: otp, + name: widget.name, + email: widget.email, + password: widget.password, + faculty: widget.faculty, + id: widget.id, + regi: widget.regi, + phone: widget.phone, + ); + + if (success) { + Get.offAllNamed(AppRoutes.dashboard); + } + } + } + + void _resendOtp(AuthController controller) async { + setState(() { + _isResending = true; + }); + + try { + await controller.sendOtpEmail(widget.email); + _startResendTimer(); + + Get.snackbar( + 'Success', + 'OTP code resent successfully!', + backgroundColor: Colors.green, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + } catch (e) { + Get.snackbar( + 'Error', + 'Failed to resend OTP. Please try again.', + backgroundColor: Colors.red, + colorText: Colors.white, + snackPosition: SnackPosition.TOP, + duration: const Duration(seconds: 3), + margin: const EdgeInsets.all(16), + borderRadius: 12, + ); + } finally { + setState(() { + _isResending = false; + }); + } + } +} diff --git a/lib/views/payment_failed_screen.dart b/lib/views/payment_failed_screen.dart index 78bf90b..d63989d 100644 --- a/lib/views/payment_failed_screen.dart +++ b/lib/views/payment_failed_screen.dart @@ -8,10 +8,10 @@ class PaymentFailedScreen extends StatefulWidget { final String errorMessage; const PaymentFailedScreen({ - Key? key, + super.key, required this.fee, required this.errorMessage, - }) : super(key: key); + }); @override State createState() => _PaymentFailedScreenState(); @@ -60,36 +60,58 @@ class _PaymentFailedScreenState extends State return Scaffold( backgroundColor: const Color(0xFFFEF2F2), body: SafeArea( - child: AnimationLimiter( - child: Column( - children: AnimationConfiguration.toStaggeredList( - duration: const Duration(milliseconds: 800), - childAnimationBuilder: - (widget) => SlideAnimation( - verticalOffset: 50.0, - child: FadeInAnimation(child: widget), - ), - children: [ - _buildHeader(), - Expanded( - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - _buildFailedIcon(), - const SizedBox(height: 32), - _buildFailedMessage(), - const SizedBox(height: 32), - _buildErrorDetails(), - const SizedBox(height: 32), - _buildActionButtons(), - ], + child: Column( + children: [ + _buildHeader(), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + final isCompact = constraints.maxHeight < 620; + final contentPadding = EdgeInsets.fromLTRB( + 20, + isCompact ? 12 : 24, + 20, + 24, + ); + + return AnimationLimiter( + child: SingleChildScrollView( + padding: contentPadding, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: + constraints.maxHeight - contentPadding.vertical, + ), + child: Column( + mainAxisAlignment: + isCompact + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: AnimationConfiguration.toStaggeredList( + duration: const Duration(milliseconds: 800), + childAnimationBuilder: + (widget) => SlideAnimation( + verticalOffset: 32.0, + child: FadeInAnimation(child: widget), + ), + children: [ + _buildFailedIcon(isCompact: isCompact), + SizedBox(height: isCompact ? 20 : 28), + _buildFailedMessage(isCompact: isCompact), + SizedBox(height: isCompact ? 20 : 28), + _buildErrorDetails(isCompact: isCompact), + SizedBox(height: isCompact ? 20 : 28), + _buildActionButtons(), + ], + ), + ), + ), ), - ), - ), - ], + ); + }, + ), ), - ), + ], ), ), ); @@ -121,15 +143,18 @@ class _PaymentFailedScreenState extends State ); } - Widget _buildFailedIcon() { + Widget _buildFailedIcon({required bool isCompact}) { + final size = isCompact ? 92.0 : 112.0; + final iconSize = isCompact ? 48.0 : 56.0; + return AnimatedBuilder( animation: _animationController, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( - width: 120, - height: 120, + width: size, + height: size, decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, @@ -139,34 +164,34 @@ class _PaymentFailedScreenState extends State shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: const Color(0xFFEF4444).withOpacity(0.3), + color: const Color(0xFFEF4444).withValues(alpha: 0.3), blurRadius: 20, offset: const Offset(0, 10), ), ], ), - child: const Icon(Icons.close, size: 60, color: Colors.white), + child: Icon(Icons.close, size: iconSize, color: Colors.white), ), ); }, ); } - Widget _buildFailedMessage() { + Widget _buildFailedMessage({required bool isCompact}) { return SlideTransition( position: _slideAnimation, child: Column( children: [ - const Text( + Text( 'Payment Failed', style: TextStyle( - fontSize: 28, + fontSize: isCompact ? 24 : 28, fontWeight: FontWeight.bold, - color: Color(0xFFEF4444), + color: const Color(0xFFEF4444), ), textAlign: TextAlign.center, ), - const SizedBox(height: 12), + SizedBox(height: isCompact ? 8 : 12), Text( 'Your payment for ${widget.fee.purpose} could not be processed.', style: const TextStyle(fontSize: 16, color: Colors.grey), @@ -177,15 +202,15 @@ class _PaymentFailedScreenState extends State ); } - Widget _buildErrorDetails() { + Widget _buildErrorDetails({required bool isCompact}) { return Container( - padding: const EdgeInsets.all(24), + padding: EdgeInsets.all(isCompact ? 18 : 22), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.1), + color: Colors.grey.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 5), ), @@ -237,6 +262,7 @@ class _PaymentFailedScreenState extends State fontWeight: FontWeight.bold, color: Colors.black87, ), + softWrap: true, ), ], ), @@ -258,7 +284,7 @@ class _PaymentFailedScreenState extends State borderRadius: BorderRadius.circular(12), ), elevation: 4, - shadowColor: const Color(0xFF3B82F6).withOpacity(0.3), + shadowColor: const Color(0xFF3B82F6).withValues(alpha: 0.3), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/views/payment_page.dart b/lib/views/payment_page.dart index e2a4553..a826831 100644 --- a/lib/views/payment_page.dart +++ b/lib/views/payment_page.dart @@ -7,7 +7,7 @@ import '../controllers/academic_fees_controller.dart'; class PaymentPage extends StatefulWidget { final AcademicFeeModel fee; - const PaymentPage({Key? key, required this.fee}) : super(key: key); + const PaymentPage({super.key, required this.fee}); @override State createState() => _PaymentPageState(); @@ -114,7 +114,7 @@ class _PaymentPageState extends State borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: const Color(0xFF6366F1).withOpacity(0.3), + color: const Color(0xFF6366F1).withValues(alpha: 0.3), blurRadius: 20, offset: const Offset(0, 10), ), @@ -137,7 +137,7 @@ class _PaymentPageState extends State 'Secure payment powered by SSLCommerz', style: TextStyle( fontSize: 14, - color: Colors.white.withOpacity(0.8), + color: Colors.white.withValues(alpha: 0.8), ), textAlign: TextAlign.center, ), @@ -158,7 +158,7 @@ class _PaymentPageState extends State borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.1), + color: Colors.grey.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 5), ), @@ -195,22 +195,31 @@ class _PaymentPageState extends State return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - label, - style: TextStyle( - fontSize: 14, - color: Colors.grey[600], - fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + Flexible( + child: Text( + label, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + ), ), ), - Text( - value, - style: TextStyle( - fontSize: 14, - fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, - color: isTotal ? const Color(0xFF6366F1) : Colors.black87, + const SizedBox(width: 12), + Flexible( + child: Text( + value, + textAlign: TextAlign.end, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14, + fontWeight: isTotal ? FontWeight.bold : FontWeight.w500, + color: isTotal ? const Color(0xFF6366F1) : Colors.black87, + ), ), ), ], @@ -226,7 +235,7 @@ class _PaymentPageState extends State borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.1), + color: Colors.grey.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 5), ), @@ -272,7 +281,7 @@ class _PaymentPageState extends State Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: const Color(0xFF6366F1).withOpacity(0.1), + color: const Color(0xFF6366F1).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: const Color(0xFF6366F1), size: 24), @@ -324,7 +333,7 @@ class _PaymentPageState extends State borderRadius: BorderRadius.circular(12), ), elevation: 4, - shadowColor: const Color(0xFF6366F1).withOpacity(0.3), + shadowColor: const Color(0xFF6366F1).withValues(alpha: 0.3), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/views/payment_success_screen.dart b/lib/views/payment_success_screen.dart index 9cf0f9e..8ab9527 100644 --- a/lib/views/payment_success_screen.dart +++ b/lib/views/payment_success_screen.dart @@ -11,11 +11,11 @@ class PaymentSuccessScreen extends StatefulWidget { final Map paymentData; const PaymentSuccessScreen({ - Key? key, + super.key, required this.fee, required this.user, required this.paymentData, - }) : super(key: key); + }); @override State createState() => _PaymentSuccessScreenState(); @@ -64,38 +64,58 @@ class _PaymentSuccessScreenState extends State return Scaffold( backgroundColor: const Color(0xFFF8FAFC), body: SafeArea( - child: AnimationLimiter( - child: SingleChildScrollView( - child: Column( - children: AnimationConfiguration.toStaggeredList( - duration: const Duration(milliseconds: 800), - childAnimationBuilder: - (widget) => SlideAnimation( - verticalOffset: 50.0, - child: FadeInAnimation(child: widget), - ), - children: [ - _buildHeader(), - Expanded( + child: Column( + children: [ + _buildHeader(), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + final isCompact = constraints.maxHeight < 680; + final contentPadding = EdgeInsets.fromLTRB( + 20, + isCompact ? 12 : 24, + 20, + 24, + ); + + return AnimationLimiter( child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( - children: [ - _buildSuccessIcon(), - const SizedBox(height: 32), - _buildSuccessMessage(), - const SizedBox(height: 32), - _buildPaymentDetails(), - const SizedBox(height: 32), - _buildActionButtons(), - ], + padding: contentPadding, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: + constraints.maxHeight - contentPadding.vertical, + ), + child: Column( + mainAxisAlignment: + isCompact + ? MainAxisAlignment.start + : MainAxisAlignment.center, + children: AnimationConfiguration.toStaggeredList( + duration: const Duration(milliseconds: 800), + childAnimationBuilder: + (widget) => SlideAnimation( + verticalOffset: 32.0, + child: FadeInAnimation(child: widget), + ), + children: [ + _buildSuccessIcon(isCompact: isCompact), + SizedBox(height: isCompact ? 20 : 28), + _buildSuccessMessage(isCompact: isCompact), + SizedBox(height: isCompact ? 20 : 28), + _buildPaymentDetails(isCompact: isCompact), + SizedBox(height: isCompact ? 20 : 28), + _buildActionButtons(), + ], + ), + ), ), ), - ), - ], + ); + }, ), ), - ), + ], ), ), ); @@ -127,15 +147,18 @@ class _PaymentSuccessScreenState extends State ); } - Widget _buildSuccessIcon() { + Widget _buildSuccessIcon({required bool isCompact}) { + final size = isCompact ? 92.0 : 112.0; + final iconSize = isCompact ? 48.0 : 56.0; + return AnimatedBuilder( animation: _animationController, builder: (context, child) { return Transform.scale( scale: _scaleAnimation.value, child: Container( - width: 120, - height: 120, + width: size, + height: size, decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, @@ -145,34 +168,34 @@ class _PaymentSuccessScreenState extends State shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: const Color(0xFF10B981).withOpacity(0.3), + color: const Color(0xFF10B981).withValues(alpha: 0.3), blurRadius: 20, offset: const Offset(0, 10), ), ], ), - child: const Icon(Icons.check, size: 60, color: Colors.white), + child: Icon(Icons.check, size: iconSize, color: Colors.white), ), ); }, ); } - Widget _buildSuccessMessage() { + Widget _buildSuccessMessage({required bool isCompact}) { return SlideTransition( position: _slideAnimation, child: Column( children: [ - const Text( + Text( 'Payment Successful!', style: TextStyle( - fontSize: 28, + fontSize: isCompact ? 24 : 28, fontWeight: FontWeight.bold, - color: Color(0xFF10B981), + color: const Color(0xFF10B981), ), textAlign: TextAlign.center, ), - const SizedBox(height: 12), + SizedBox(height: isCompact ? 8 : 12), Text( 'Your payment for ${widget.fee.purpose} has been processed successfully.', style: const TextStyle(fontSize: 16, color: Colors.grey), @@ -183,15 +206,15 @@ class _PaymentSuccessScreenState extends State ); } - Widget _buildPaymentDetails() { + Widget _buildPaymentDetails({required bool isCompact}) { return Container( - padding: const EdgeInsets.all(24), + padding: EdgeInsets.all(isCompact ? 18 : 22), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(20), + borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.1), + color: Colors.grey.withValues(alpha: 0.1), blurRadius: 20, offset: const Offset(0, 5), ), @@ -283,7 +306,7 @@ class _PaymentSuccessScreenState extends State borderRadius: BorderRadius.circular(12), ), elevation: 4, - shadowColor: const Color(0xFF3B82F6).withOpacity(0.3), + shadowColor: const Color(0xFF3B82F6).withValues(alpha: 0.3), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, @@ -311,7 +334,7 @@ class _PaymentSuccessScreenState extends State borderRadius: BorderRadius.circular(12), ), elevation: 4, - shadowColor: const Color(0xFF10B981).withOpacity(0.3), + shadowColor: const Color(0xFF10B981).withValues(alpha: 0.3), ), child: const Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/views/sign_in_view.dart b/lib/views/sign_in_view.dart index 49af377..b7c3af4 100644 --- a/lib/views/sign_in_view.dart +++ b/lib/views/sign_in_view.dart @@ -1,248 +1,248 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:myedu/components/custom_button.dart'; -import 'package:myedu/components/custom_text_field.dart'; -import 'package:myedu/constraints/app_constants.dart'; -import 'package:myedu/controllers/auth_controller.dart'; -import 'package:myedu/controllers/theme_controller.dart'; -import 'package:myedu/views/sign_up_view.dart'; - -class SignInView extends StatefulWidget { - const SignInView({super.key}); - - @override - State createState() => _SignInViewState(); -} - -class _SignInViewState extends State with TickerProviderStateMixin { - final _formKey = GlobalKey(); - final _emailController = TextEditingController(); - final _passwordController = TextEditingController(); - - late AnimationController _fadeController; - late AnimationController _slideController; - late Animation _fadeAnimation; - late Animation _slideAnimation; - - final AuthController _authController = Get.find(); - - @override - void initState() { - super.initState(); - _setupAnimations(); - } - - void _setupAnimations() { - _fadeController = AnimationController( - duration: const Duration(milliseconds: 800), - vsync: this, - ); - _slideController = AnimationController( - duration: const Duration(milliseconds: 1000), - vsync: this, - ); - - _fadeAnimation = Tween( - begin: 0.0, - end: 1.0, - ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); - - _slideAnimation = Tween( - begin: const Offset(0, 0.3), - end: Offset.zero, - ).animate( - CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack), - ); - - _fadeController.forward(); - _slideController.forward(); - } - - @override - void dispose() { - _fadeController.dispose(); - _slideController.dispose(); - _emailController.dispose(); - _passwordController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SafeArea( - child: SingleChildScrollView( - padding: const EdgeInsets.all(AppConstants.mediumSpacing), - child: FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildHeader(), - const SizedBox(height: AppConstants.extraLargeSpacing * 2), - _buildSignInForm(), - const SizedBox(height: AppConstants.largeSpacing), - _buildSignUpLink(), - ], - ), - ), - ), - ), - ), - ); - } - - Widget _buildHeader() { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SizedBox(width: 48), - Text( - AppConstants.appName, - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, - ), - ), - GetBuilder( - builder: (controller) { - return IconButton( - icon: Icon( - controller.isDarkMode ? Icons.light_mode : Icons.dark_mode, - ), - onPressed: controller.toggleTheme, - ); - }, - ), - ], - ), - const SizedBox(height: AppConstants.mediumSpacing), - Container( - height: 120, - width: 120, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.school, - size: 60, - color: Theme.of(context).primaryColor, - ), - ), - const SizedBox(height: AppConstants.mediumSpacing), - Text( - 'Welcome Back!', - style: Theme.of( - context, - ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w600), - ), - const SizedBox(height: AppConstants.smallSpacing), - Text( - 'Sign in to your university account', - style: Theme.of( - context, - ).textTheme.bodyLarge?.copyWith(color: Colors.grey[600]), - textAlign: TextAlign.center, - ), - ], - ); - } - - Widget _buildSignInForm() { - return Card( - elevation: AppConstants.mediumElevation, - child: Padding( - padding: const EdgeInsets.all(AppConstants.largeSpacing), - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'Sign In', - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppConstants.largeSpacing), - CustomTextField( - controller: _emailController, - label: 'Email', - hint: 'Enter your email address', - isEmail: true, - prefixIcon: Icons.email_outlined, - keyboardType: TextInputType.emailAddress, - ), - const SizedBox(height: AppConstants.mediumSpacing), - CustomTextField( - controller: _passwordController, - label: 'Password', - hint: 'Enter your password', - isPassword: true, - prefixIcon: Icons.lock_outline, - validator: (value) { - if (value == null || value.isEmpty) { - return AppConstants.passwordRequired; - } - return null; - }, - ), - const SizedBox(height: AppConstants.largeSpacing), - GetBuilder( - builder: (controller) { - return CustomButton( - text: 'Sign In', - onPressed: _signIn, - isLoading: controller.isLoading, - icon: Icons.login, - ); - }, - ), - ], - ), - ), - ), - ); - } - - Widget _buildSignUpLink() { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Don\'t have an account? ', - style: Theme.of(context).textTheme.bodyMedium, - ), - TextButton( - onPressed: () => Get.to(() => SignUpView()), - child: Text( - 'Sign Up', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ); - } - - void _signIn() async { - if (_formKey.currentState!.validate()) { - bool success = await _authController.signIn( - email: _emailController.text.trim(), - password: _passwordController.text.trim(), - ); - - if (success) { - Get.offAllNamed(AppRoutes.dashboard); - } - } - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:myedu/components/custom_button.dart'; +import 'package:myedu/components/custom_text_field.dart'; +import 'package:myedu/constraints/app_constants.dart'; +import 'package:myedu/controllers/auth_controller.dart'; +import 'package:myedu/controllers/theme_controller.dart'; +import 'package:myedu/views/sign_up_view.dart'; + +class SignInView extends StatefulWidget { + const SignInView({super.key}); + + @override + State createState() => _SignInViewState(); +} + +class _SignInViewState extends State with TickerProviderStateMixin { + final _formKey = GlobalKey(); + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + + late AnimationController _fadeController; + late AnimationController _slideController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + + final AuthController _authController = Get.find(); + + @override + void initState() { + super.initState(); + _setupAnimations(); + } + + void _setupAnimations() { + _fadeController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + _slideController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); + + _slideAnimation = Tween( + begin: const Offset(0, 0.3), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack), + ); + + _fadeController.forward(); + _slideController.forward(); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.all(AppConstants.mediumSpacing), + child: FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeader(), + const SizedBox(height: AppConstants.extraLargeSpacing * 2), + _buildSignInForm(), + const SizedBox(height: AppConstants.largeSpacing), + _buildSignUpLink(), + ], + ), + ), + ), + ), + ), + ); + } + + Widget _buildHeader() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox(width: 48), + Text( + AppConstants.appName, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryColor, + ), + ), + GetBuilder( + builder: (controller) { + return IconButton( + icon: Icon( + controller.isDarkMode ? Icons.light_mode : Icons.dark_mode, + ), + onPressed: controller.toggleTheme, + ); + }, + ), + ], + ), + const SizedBox(height: AppConstants.mediumSpacing), + Container( + height: 120, + width: 120, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.school, + size: 60, + color: Theme.of(context).primaryColor, + ), + ), + const SizedBox(height: AppConstants.mediumSpacing), + Text( + 'Welcome Back!', + style: Theme.of( + context, + ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: AppConstants.smallSpacing), + Text( + 'Sign in to your university account', + style: Theme.of( + context, + ).textTheme.bodyLarge?.copyWith(color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + ], + ); + } + + Widget _buildSignInForm() { + return Card( + elevation: AppConstants.mediumElevation, + child: Padding( + padding: const EdgeInsets.all(AppConstants.largeSpacing), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Sign In', + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppConstants.largeSpacing), + CustomTextField( + controller: _emailController, + label: 'Email', + hint: 'Enter your email address', + isEmail: true, + prefixIcon: Icons.email_outlined, + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: AppConstants.mediumSpacing), + CustomTextField( + controller: _passwordController, + label: 'Password', + hint: 'Enter your password', + isPassword: true, + prefixIcon: Icons.lock_outline, + validator: (value) { + if (value == null || value.isEmpty) { + return AppConstants.passwordRequired; + } + return null; + }, + ), + const SizedBox(height: AppConstants.largeSpacing), + GetBuilder( + builder: (controller) { + return CustomButton( + text: 'Sign In', + onPressed: _signIn, + isLoading: controller.isLoading, + icon: Icons.login, + ); + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildSignUpLink() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Don\'t have an account? ', + style: Theme.of(context).textTheme.bodyMedium, + ), + TextButton( + onPressed: () => Get.to(() => SignUpView()), + child: Text( + 'Sign Up', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ); + } + + void _signIn() async { + if (_formKey.currentState!.validate()) { + bool success = await _authController.signIn( + email: _emailController.text.trim(), + password: _passwordController.text.trim(), + ); + + if (success) { + Get.offAllNamed(AppRoutes.dashboard); + } + } + } +} diff --git a/lib/views/sign_up_view.dart b/lib/views/sign_up_view.dart index 363b6ae..2659c91 100644 --- a/lib/views/sign_up_view.dart +++ b/lib/views/sign_up_view.dart @@ -1,339 +1,339 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:myedu/components/custom_button.dart'; -import 'package:myedu/components/custom_dropdown.dart'; -import 'package:myedu/components/custom_text_field.dart'; -import 'package:myedu/constraints/app_constants.dart'; -import 'package:myedu/controllers/auth_controller.dart'; -import 'package:myedu/controllers/theme_controller.dart'; - -class SignUpView extends StatefulWidget { - const SignUpView({super.key}); - - @override - State createState() => _SignUpViewState(); -} - -class _SignUpViewState extends State with TickerProviderStateMixin { - late AnimationController _fadeController; - late AnimationController _slideController; - late Animation _fadeAnimation; - late Animation _slideAnimation; - - @override - void initState() { - super.initState(); - _setupAnimations(); - } - - void _setupAnimations() { - _fadeController = AnimationController( - duration: const Duration(milliseconds: 800), - vsync: this, - ); - _slideController = AnimationController( - duration: const Duration(milliseconds: 1000), - vsync: this, - ); - - _fadeAnimation = Tween( - begin: 0.0, - end: 1.0, - ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); - - _slideAnimation = Tween( - begin: const Offset(0, 0.3), - end: Offset.zero, - ).animate( - CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack), - ); - - _fadeController.forward(); - _slideController.forward(); - } - - @override - void dispose() { - _fadeController.dispose(); - _slideController.dispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - // Ensure AuthController is initialized - // if (!Get.isRegistered()) { - // Get.put(AuthController()); - // } - - return Scaffold( - appBar: AppBar( - title: const Text('Create Account'), - actions: [ - GetBuilder( - builder: (controller) { - return IconButton( - icon: Icon( - controller.isDarkMode ? Icons.light_mode : Icons.dark_mode, - ), - onPressed: controller.toggleTheme, - ); - }, - ), - ], - ), - body: SafeArea( - child: GetBuilder( - init: AuthController(), - builder: (controller) { - return SingleChildScrollView( - padding: const EdgeInsets.all(AppConstants.mediumSpacing), - child: FadeTransition( - opacity: _fadeAnimation, - child: SlideTransition( - position: _slideAnimation, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - _buildHeader(controller), - const SizedBox(height: AppConstants.largeSpacing), - _buildSignUpForm(controller), - const SizedBox(height: AppConstants.largeSpacing), - _buildSignInLink(controller), - ], - ), - ), - ), - ); - }, - ), - ), - ); - } - - Widget _buildHeader(AuthController controller) { - return Column( - children: [ - Container( - height: 80, - width: 80, - decoration: BoxDecoration( - color: Theme.of(context).primaryColor.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: Icon( - Icons.person_add, - size: 40, - color: Theme.of(context).primaryColor, - ), - ), - const SizedBox(height: AppConstants.mediumSpacing), - Text( - 'Join ${AppConstants.appName}', - style: Theme.of( - context, - ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w600), - ), - const SizedBox(height: AppConstants.smallSpacing), - Text( - 'Create your university account', - style: Theme.of( - context, - ).textTheme.bodyLarge?.copyWith(color: Colors.grey[600]), - textAlign: TextAlign.center, - ), - ], - ); - } - - Widget _buildSignUpForm(AuthController controller) { - return Card( - elevation: AppConstants.mediumElevation, - child: Padding( - padding: const EdgeInsets.all(AppConstants.largeSpacing), - child: Form( - key: controller.formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - 'Personal Information', - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - textAlign: TextAlign.center, - ), - const SizedBox(height: AppConstants.largeSpacing), - - // Name Field - CustomTextField( - controller: controller.nameController, - label: 'Full Name', - hint: 'Enter your full name', - prefixIcon: Icons.person_outline, - validator: (value) { - if (value == null || value.isEmpty) { - return AppConstants.nameRequired; - } - return null; - }, - ), - const SizedBox(height: AppConstants.mediumSpacing), - - // Email Field - CustomTextField( - controller: controller.emailController, - label: 'Email', - hint: 'Enter your email address', - isEmail: true, - prefixIcon: Icons.email_outlined, - keyboardType: TextInputType.emailAddress, - ), - const SizedBox(height: AppConstants.mediumSpacing), - - // Faculty Dropdown - CustomDropdown( - label: 'Faculty', - value: controller.selectedFaculty, - items: AppConstants.faculties, - onChanged: (value) { - setState(() { - controller.selectedFaculty = value; - }); - }, - prefixIcon: Icons.school_outlined, - hint: 'Select your faculty', - ), - const SizedBox(height: AppConstants.mediumSpacing), - - // ID Field - CustomTextField( - controller: controller.idController, - label: 'Student ID', - hint: 'Enter your student ID', - prefixIcon: Icons.badge_outlined, - keyboardType: TextInputType.text, - validator: (value) { - if (value == null || value.isEmpty) { - return AppConstants.idRequired; - } - return null; - }, - ), - const SizedBox(height: AppConstants.mediumSpacing), - - // Registration Field - CustomTextField( - controller: controller.regiController, - label: 'Registration Number', - hint: 'Enter your registration number', - prefixIcon: Icons.assignment_outlined, - keyboardType: TextInputType.text, - validator: (value) { - if (value == null || value.isEmpty) { - return AppConstants.regiRequired; - } - return null; - }, - ), - const SizedBox(height: AppConstants.mediumSpacing), - - // Phone Field - CustomTextField( - controller: controller.phoneController, - label: 'Phone Number', - hint: 'Enter your phone number', - isPhone: true, - prefixIcon: Icons.phone_outlined, - keyboardType: TextInputType.phone, - validator: (value) { - if (value == null || value.isEmpty) { - return AppConstants.phoneRequired; - } - if (!GetUtils.isPhoneNumber(value)) { - return AppConstants.phoneInvalid; - } - return null; - }, - ), - const SizedBox(height: AppConstants.mediumSpacing), - - // Password Field - CustomTextField( - controller: controller.passwordController, - label: 'Password', - hint: 'Create a strong password', - isPassword: true, - prefixIcon: Icons.lock_outline, - validator: (value) { - if (value == null || value.isEmpty) { - return AppConstants.passwordRequired; - } - if (value.length < 6) { - return AppConstants.passwordMinLength; - } - return null; - }, - ), - const SizedBox(height: AppConstants.mediumSpacing), - - // Confirm Password Field - CustomTextField( - controller: controller.confirmPasswordController, - label: 'Confirm Password', - hint: 'Re-enter your password', - isPassword: true, - prefixIcon: Icons.lock_outline, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please confirm your password'; - } - if (value != controller.passwordController.text) { - return AppConstants.passwordMismatch; - } - return null; - }, - ), - const SizedBox(height: AppConstants.largeSpacing), - - // Sign Up Button - GetBuilder( - builder: (controller) { - return CustomButton( - text: 'Create Account', - onPressed: controller.signUpHandler, - isLoading: controller.isLoading, - icon: Icons.person_add, - ); - }, - ), - ], - ), - ), - ), - ); - } - - Widget _buildSignInLink(AuthController controller) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Already have an account? ', - style: Theme.of(context).textTheme.bodyMedium, - ), - TextButton( - onPressed: () => Get.back(), - child: Text( - 'Sign In', - style: TextStyle( - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ); - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:myedu/components/custom_button.dart'; +import 'package:myedu/components/custom_dropdown.dart'; +import 'package:myedu/components/custom_text_field.dart'; +import 'package:myedu/constraints/app_constants.dart'; +import 'package:myedu/controllers/auth_controller.dart'; +import 'package:myedu/controllers/theme_controller.dart'; + +class SignUpView extends StatefulWidget { + const SignUpView({super.key}); + + @override + State createState() => _SignUpViewState(); +} + +class _SignUpViewState extends State with TickerProviderStateMixin { + late AnimationController _fadeController; + late AnimationController _slideController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + + @override + void initState() { + super.initState(); + _setupAnimations(); + } + + void _setupAnimations() { + _fadeController = AnimationController( + duration: const Duration(milliseconds: 800), + vsync: this, + ); + _slideController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeIn)); + + _slideAnimation = Tween( + begin: const Offset(0, 0.3), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _slideController, curve: Curves.easeOutBack), + ); + + _fadeController.forward(); + _slideController.forward(); + } + + @override + void dispose() { + _fadeController.dispose(); + _slideController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + // Ensure AuthController is initialized + // if (!Get.isRegistered()) { + // Get.put(AuthController()); + // } + + return Scaffold( + appBar: AppBar( + title: const Text('Create Account'), + actions: [ + GetBuilder( + builder: (controller) { + return IconButton( + icon: Icon( + controller.isDarkMode ? Icons.light_mode : Icons.dark_mode, + ), + onPressed: controller.toggleTheme, + ); + }, + ), + ], + ), + body: SafeArea( + child: GetBuilder( + init: AuthController(), + builder: (controller) { + return SingleChildScrollView( + padding: const EdgeInsets.all(AppConstants.mediumSpacing), + child: FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildHeader(controller), + const SizedBox(height: AppConstants.largeSpacing), + _buildSignUpForm(controller), + const SizedBox(height: AppConstants.largeSpacing), + _buildSignInLink(controller), + ], + ), + ), + ), + ); + }, + ), + ), + ); + } + + Widget _buildHeader(AuthController controller) { + return Column( + children: [ + Container( + height: 80, + width: 80, + decoration: BoxDecoration( + color: Theme.of(context).primaryColor.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Icon( + Icons.person_add, + size: 40, + color: Theme.of(context).primaryColor, + ), + ), + const SizedBox(height: AppConstants.mediumSpacing), + Text( + 'Join ${AppConstants.appName}', + style: Theme.of( + context, + ).textTheme.headlineMedium?.copyWith(fontWeight: FontWeight.w600), + ), + const SizedBox(height: AppConstants.smallSpacing), + Text( + 'Create your university account', + style: Theme.of( + context, + ).textTheme.bodyLarge?.copyWith(color: Colors.grey[600]), + textAlign: TextAlign.center, + ), + ], + ); + } + + Widget _buildSignUpForm(AuthController controller) { + return Card( + elevation: AppConstants.mediumElevation, + child: Padding( + padding: const EdgeInsets.all(AppConstants.largeSpacing), + child: Form( + key: controller.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + 'Personal Information', + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox(height: AppConstants.largeSpacing), + + // Name Field + CustomTextField( + controller: controller.nameController, + label: 'Full Name', + hint: 'Enter your full name', + prefixIcon: Icons.person_outline, + validator: (value) { + if (value == null || value.isEmpty) { + return AppConstants.nameRequired; + } + return null; + }, + ), + const SizedBox(height: AppConstants.mediumSpacing), + + // Email Field + CustomTextField( + controller: controller.emailController, + label: 'Email', + hint: 'Enter your email address', + isEmail: true, + prefixIcon: Icons.email_outlined, + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: AppConstants.mediumSpacing), + + // Faculty Dropdown + CustomDropdown( + label: 'Faculty', + value: controller.selectedFaculty, + items: AppConstants.faculties, + onChanged: (value) { + setState(() { + controller.selectedFaculty = value; + }); + }, + prefixIcon: Icons.school_outlined, + hint: 'Select your faculty', + ), + const SizedBox(height: AppConstants.mediumSpacing), + + // ID Field + CustomTextField( + controller: controller.idController, + label: 'Student ID', + hint: 'Enter your student ID', + prefixIcon: Icons.badge_outlined, + keyboardType: TextInputType.text, + validator: (value) { + if (value == null || value.isEmpty) { + return AppConstants.idRequired; + } + return null; + }, + ), + const SizedBox(height: AppConstants.mediumSpacing), + + // Registration Field + CustomTextField( + controller: controller.regiController, + label: 'Registration Number', + hint: 'Enter your registration number', + prefixIcon: Icons.assignment_outlined, + keyboardType: TextInputType.text, + validator: (value) { + if (value == null || value.isEmpty) { + return AppConstants.regiRequired; + } + return null; + }, + ), + const SizedBox(height: AppConstants.mediumSpacing), + + // Phone Field + CustomTextField( + controller: controller.phoneController, + label: 'Phone Number', + hint: 'Enter your phone number', + isPhone: true, + prefixIcon: Icons.phone_outlined, + keyboardType: TextInputType.phone, + validator: (value) { + if (value == null || value.isEmpty) { + return AppConstants.phoneRequired; + } + if (!GetUtils.isPhoneNumber(value)) { + return AppConstants.phoneInvalid; + } + return null; + }, + ), + const SizedBox(height: AppConstants.mediumSpacing), + + // Password Field + CustomTextField( + controller: controller.passwordController, + label: 'Password', + hint: 'Create a strong password', + isPassword: true, + prefixIcon: Icons.lock_outline, + validator: (value) { + if (value == null || value.isEmpty) { + return AppConstants.passwordRequired; + } + if (value.length < 6) { + return AppConstants.passwordMinLength; + } + return null; + }, + ), + const SizedBox(height: AppConstants.mediumSpacing), + + // Confirm Password Field + CustomTextField( + controller: controller.confirmPasswordController, + label: 'Confirm Password', + hint: 'Re-enter your password', + isPassword: true, + prefixIcon: Icons.lock_outline, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please confirm your password'; + } + if (value != controller.passwordController.text) { + return AppConstants.passwordMismatch; + } + return null; + }, + ), + const SizedBox(height: AppConstants.largeSpacing), + + // Sign Up Button + GetBuilder( + builder: (controller) { + return CustomButton( + text: 'Create Account', + onPressed: controller.signUpHandler, + isLoading: controller.isLoading, + icon: Icons.person_add, + ); + }, + ), + ], + ), + ), + ), + ); + } + + Widget _buildSignInLink(AuthController controller) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Already have an account? ', + style: Theme.of(context).textTheme.bodyMedium, + ), + TextButton( + onPressed: () => Get.back(), + child: Text( + 'Sign In', + style: TextStyle( + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ); + } +} diff --git a/lib/views/splash_view.dart b/lib/views/splash_view.dart index 59f118c..5c14b20 100644 --- a/lib/views/splash_view.dart +++ b/lib/views/splash_view.dart @@ -1,183 +1,183 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:myedu/constraints/app_constants.dart'; -import 'package:myedu/controllers/auth_controller.dart'; -import 'package:myedu/views/authgate.dart'; - -class SplashView extends StatefulWidget { - const SplashView({super.key}); - - @override - State createState() => _SplashViewState(); -} - -class _SplashViewState extends State with TickerProviderStateMixin { - late AnimationController _logoController; - late AnimationController _textController; - late Animation _logoAnimation; - late Animation _textAnimation; - late Animation _slideAnimation; - - @override - void initState() { - super.initState(); - _setupAnimations(); - _initializeApp(); - } - - void _setupAnimations() { - _logoController = AnimationController( - duration: const Duration(milliseconds: 1500), - vsync: this, - ); - - _textController = AnimationController( - duration: const Duration(milliseconds: 1000), - vsync: this, - ); - - _logoAnimation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _logoController, curve: Curves.elasticOut), - ); - - _textAnimation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: _textController, curve: Curves.easeInOut), - ); - - _slideAnimation = Tween( - begin: const Offset(0, 0.5), - end: Offset.zero, - ).animate( - CurvedAnimation(parent: _textController, curve: Curves.easeOutBack), - ); - - _logoController.forward(); - - Future.delayed(const Duration(milliseconds: 500), () { - _textController.forward(); - }); - } - - void _initializeApp() async { - // Initialize AuthController - - // Wait for animations to complete - await Future.delayed(const Duration(seconds: 3)); - - // Navigate to Authgate - Get.off(() => Authgate()); - } - - @override - void dispose() { - _logoController.dispose(); - _textController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - width: double.infinity, - height: double.infinity, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - Theme.of(context).primaryColor, - Theme.of(context).primaryColor.withOpacity(0.8), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - ), - child: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Logo Animation - ScaleTransition( - scale: _logoAnimation, - child: Container( - height: 120, - width: 120, - decoration: const BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, - ), - child: Icon( - Icons.school, - size: 60, - color: Theme.of(context).primaryColor, - ), - ), - ), - const SizedBox(height: AppConstants.largeSpacing), - - // App Name Animation - FadeTransition( - opacity: _textAnimation, - child: SlideTransition( - position: _slideAnimation, - child: Column( - children: [ - Text( - AppConstants.appName, - style: const TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Colors.white, - letterSpacing: 2, - ), - ), - const SizedBox(height: AppConstants.smallSpacing), - Text( - 'University Management System', - style: TextStyle( - fontSize: 16, - color: Colors.white.withOpacity(0.9), - fontWeight: FontWeight.w400, - ), - textAlign: TextAlign.center, - ), - ], - ), - ), - ), - ], - ), - ), - - // Loading Animation - FadeTransition( - opacity: _textAnimation, - child: Column( - children: [ - const CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation(Colors.white), - strokeWidth: 3, - ), - const SizedBox(height: AppConstants.mediumSpacing), - Text( - 'Loading...', - style: TextStyle( - color: Colors.white.withOpacity(0.8), - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: AppConstants.largeSpacing), - ], - ), - ), - ], - ), - ), - ), - ); - } -} +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:myedu/constraints/app_constants.dart'; +import 'package:myedu/controllers/auth_controller.dart'; +import 'package:myedu/views/authgate.dart'; + +class SplashView extends StatefulWidget { + const SplashView({super.key}); + + @override + State createState() => _SplashViewState(); +} + +class _SplashViewState extends State with TickerProviderStateMixin { + late AnimationController _logoController; + late AnimationController _textController; + late Animation _logoAnimation; + late Animation _textAnimation; + late Animation _slideAnimation; + + @override + void initState() { + super.initState(); + _setupAnimations(); + _initializeApp(); + } + + void _setupAnimations() { + _logoController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + + _textController = AnimationController( + duration: const Duration(milliseconds: 1000), + vsync: this, + ); + + _logoAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _logoController, curve: Curves.elasticOut), + ); + + _textAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _textController, curve: Curves.easeInOut), + ); + + _slideAnimation = Tween( + begin: const Offset(0, 0.5), + end: Offset.zero, + ).animate( + CurvedAnimation(parent: _textController, curve: Curves.easeOutBack), + ); + + _logoController.forward(); + + Future.delayed(const Duration(milliseconds: 500), () { + _textController.forward(); + }); + } + + void _initializeApp() async { + // Initialize AuthController + + // Wait for animations to complete + await Future.delayed(const Duration(seconds: 3)); + + // Navigate to Authgate + Get.off(() => Authgate()); + } + + @override + void dispose() { + _logoController.dispose(); + _textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).primaryColor, + Theme.of(context).primaryColor.withOpacity(0.8), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Logo Animation + ScaleTransition( + scale: _logoAnimation, + child: Container( + height: 120, + width: 120, + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: Icon( + Icons.school, + size: 60, + color: Theme.of(context).primaryColor, + ), + ), + ), + const SizedBox(height: AppConstants.largeSpacing), + + // App Name Animation + FadeTransition( + opacity: _textAnimation, + child: SlideTransition( + position: _slideAnimation, + child: Column( + children: [ + Text( + AppConstants.appName, + style: const TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Colors.white, + letterSpacing: 2, + ), + ), + const SizedBox(height: AppConstants.smallSpacing), + Text( + 'University Management System', + style: TextStyle( + fontSize: 16, + color: Colors.white.withOpacity(0.9), + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], + ), + ), + + // Loading Animation + FadeTransition( + opacity: _textAnimation, + child: Column( + children: [ + const CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + strokeWidth: 3, + ), + const SizedBox(height: AppConstants.mediumSpacing), + Text( + 'Loading...', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: AppConstants.largeSpacing), + ], + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/linux/.gitignore b/linux/.gitignore index d3896c9..c7ea17f 100644 --- a/linux/.gitignore +++ b/linux/.gitignore @@ -1 +1 @@ -flutter/ephemeral +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 1168285..8f804a4 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -1,128 +1,128 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.13) -project(runner LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "myedu") -# The unique GTK application identifier for this application. See: -# https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.myedu") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(SET CMP0063 NEW) - -# Load bundled libraries from the lib/ directory relative to the binary. -set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") - -# Root filesystem for cross-building. -if(FLUTTER_TARGET_PLATFORM_SYSROOT) - set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) - set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -endif() - -# Define build configuration options. -if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") -endif() - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_14) - target_compile_options(${TARGET} PRIVATE -Wall -Werror) - target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") - target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) - -# Only the install-generated bundle's copy of the executable will launch -# correctly, since the resources must in the right relative locations. To avoid -# people trying to run the unbundled copy, put it in a subdirectory instead of -# the default top-level location. -set_target_properties(${BINARY_NAME} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" -) - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# By default, "installing" just makes a relocatable bundle in the build -# directory. -set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -# Start with a clean build bundle directory every time. -install(CODE " - file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") - " COMPONENT Runtime) - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) - install(FILES "${bundled_library}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endforeach(bundled_library) - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") - install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "myedu") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.myedu") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt index d5bd016..27860e8 100644 --- a/linux/flutter/CMakeLists.txt +++ b/linux/flutter/CMakeLists.txt @@ -1,88 +1,88 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.10) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. - -# Serves the same purpose as list(TRANSFORM ... PREPEND ...), -# which isn't available in 3.10. -function(list_prepend LIST_NAME PREFIX) - set(NEW_LIST "") - foreach(element ${${LIST_NAME}}) - list(APPEND NEW_LIST "${PREFIX}${element}") - endforeach(element) - set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) -endfunction() - -# === Flutter Library === -# System-level dependencies. -find_package(PkgConfig REQUIRED) -pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) -pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) -pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) - -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "fl_basic_message_channel.h" - "fl_binary_codec.h" - "fl_binary_messenger.h" - "fl_dart_project.h" - "fl_engine.h" - "fl_json_message_codec.h" - "fl_json_method_codec.h" - "fl_message_codec.h" - "fl_method_call.h" - "fl_method_channel.h" - "fl_method_codec.h" - "fl_method_response.h" - "fl_plugin_registrar.h" - "fl_plugin_registry.h" - "fl_standard_message_codec.h" - "fl_standard_method_codec.h" - "fl_string_codec.h" - "fl_value.h" - "fl_view.h" - "flutter_linux.h" -) -list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") -target_link_libraries(flutter INTERFACE - PkgConfig::GTK - PkgConfig::GLIB - PkgConfig::GIO -) -add_dependencies(flutter flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CMAKE_CURRENT_BINARY_DIR}/_phony_ - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" - ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} -) +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt index e97dabc..7ed6f3e 100644 --- a/linux/runner/CMakeLists.txt +++ b/linux/runner/CMakeLists.txt @@ -1,26 +1,26 @@ -cmake_minimum_required(VERSION 3.13) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} - "main.cc" - "my_application.cc" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the application ID. -add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") - -# Add dependency libraries. Add any application-specific dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter) -target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) - -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc index e7c5c54..4340ffc 100644 --- a/linux/runner/main.cc +++ b/linux/runner/main.cc @@ -1,6 +1,6 @@ -#include "my_application.h" - -int main(int argc, char** argv) { - g_autoptr(MyApplication) app = my_application_new(); - return g_application_run(G_APPLICATION(app), argc, argv); -} +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc index b02e0d9..de74e0d 100644 --- a/linux/runner/my_application.cc +++ b/linux/runner/my_application.cc @@ -1,130 +1,130 @@ -#include "my_application.h" - -#include -#ifdef GDK_WINDOWING_X11 -#include -#endif - -#include "flutter/generated_plugin_registrant.h" - -struct _MyApplication { - GtkApplication parent_instance; - char** dart_entrypoint_arguments; -}; - -G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) - -// Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = - GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); - - // Use a header bar when running in GNOME as this is the common style used - // by applications and is the setup most users will be using (e.g. Ubuntu - // desktop). - // If running on X and not using GNOME then just use a traditional title bar - // in case the window manager does more exotic layout, e.g. tiling. - // If running on Wayland assume the header bar will work (may need changing - // if future cases occur). - gboolean use_header_bar = TRUE; -#ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { - use_header_bar = FALSE; - } - } -#endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); - gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "myedu"); - gtk_header_bar_set_show_close_button(header_bar, TRUE); - gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "myedu"); - } - - gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); - - g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); - - FlView* view = fl_view_new(project); - gtk_widget_show(GTK_WIDGET(view)); - gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); - - fl_register_plugins(FL_PLUGIN_REGISTRY(view)); - - gtk_widget_grab_focus(GTK_WIDGET(view)); -} - -// Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { - MyApplication* self = MY_APPLICATION(application); - // Strip out the first argument as it is the binary name. - self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); - - g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; - } - - g_application_activate(application); - *exit_status = 0; - - return TRUE; -} - -// Implements GApplication::startup. -static void my_application_startup(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); - - // Perform any actions required at application startup. - - G_APPLICATION_CLASS(my_application_parent_class)->startup(application); -} - -// Implements GApplication::shutdown. -static void my_application_shutdown(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); - - // Perform any actions required at application shutdown. - - G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); -} - -// Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); - g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); - G_OBJECT_CLASS(my_application_parent_class)->dispose(object); -} - -static void my_application_class_init(MyApplicationClass* klass) { - G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; - G_APPLICATION_CLASS(klass)->startup = my_application_startup; - G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; - G_OBJECT_CLASS(klass)->dispose = my_application_dispose; -} - -static void my_application_init(MyApplication* self) {} - -MyApplication* my_application_new() { - // Set the program name to the application ID, which helps various systems - // like GTK and desktop environments map this running application to its - // corresponding .desktop file. This ensures better integration by allowing - // the application to be recognized beyond its binary name. - g_set_prgname(APPLICATION_ID); - - return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - "flags", G_APPLICATION_NON_UNIQUE, - nullptr)); -} +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "myedu"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "myedu"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h index 72271d5..8f20fb5 100644 --- a/linux/runner/my_application.h +++ b/linux/runner/my_application.h @@ -1,18 +1,18 @@ -#ifndef FLUTTER_MY_APPLICATION_H_ -#define FLUTTER_MY_APPLICATION_H_ - -#include - -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, - GtkApplication) - -/** - * my_application_new: - * - * Creates a new Flutter-based application. - * - * Returns: a new #MyApplication. - */ -MyApplication* my_application_new(); - -#endif // FLUTTER_MY_APPLICATION_H_ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore index 746adbb..d4e0569 100644 --- a/macos/.gitignore +++ b/macos/.gitignore @@ -1,7 +1,7 @@ -# Flutter-related -**/Flutter/ephemeral/ -**/Pods/ - -# Xcode-related -**/dgph -**/xcuserdata/ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index 4b81f9b..63eaa61 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1,2 +1,2 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index 5caa9d1..88d14e0 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1,2 +1,2 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Podfile b/macos/Podfile index 29c8eb3..83b21e9 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,42 +1,42 @@ -platform :osx, '10.14' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end +platform :osx, '10.14' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index 4ead3cf..e65ff7b 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -1,705 +1,705 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXAggregateTarget section */ - 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; - buildPhases = ( - 33CC111E2044C6BF0003C045 /* ShellScript */, - ); - dependencies = ( - ); - name = "Flutter Assemble"; - productName = FLX; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC10EC2044A3C60003C045; - remoteInfo = Runner; - }; - 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 33CC10E52044A3C60003C045 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 33CC111A2044C6BA0003C045; - remoteInfo = FLX; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Bundle Framework"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* myedu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "myedu.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; - 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; - 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; - 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; - 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 331C80D2294CF70F00263BE5 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EA2044A3C60003C045 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C80D6294CF71000263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C80D7294CF71000263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 33BA886A226E78AF003329D5 /* Configs */ = { - isa = PBXGroup; - children = ( - 33E5194F232828860026EE4D /* AppInfo.xcconfig */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, - ); - path = Configs; - sourceTree = ""; - }; - 33CC10E42044A3C60003C045 = { - isa = PBXGroup; - children = ( - 33FAB671232836740065AC1E /* Runner */, - 33CEB47122A05771004F2AC0 /* Flutter */, - 331C80D6294CF71000263BE5 /* RunnerTests */, - 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - ); - sourceTree = ""; - }; - 33CC10EE2044A3C60003C045 /* Products */ = { - isa = PBXGroup; - children = ( - 33CC10ED2044A3C60003C045 /* myedu.app */, - 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 33CC11242044D66E0003C045 /* Resources */ = { - isa = PBXGroup; - children = ( - 33CC10F22044A3C60003C045 /* Assets.xcassets */, - 33CC10F42044A3C60003C045 /* MainMenu.xib */, - 33CC10F72044A3C60003C045 /* Info.plist */, - ); - name = Resources; - path = ..; - sourceTree = ""; - }; - 33CEB47122A05771004F2AC0 /* Flutter */ = { - isa = PBXGroup; - children = ( - 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, - 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, - 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, - 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - ); - path = Flutter; - sourceTree = ""; - }; - 33FAB671232836740065AC1E /* Runner */ = { - isa = PBXGroup; - children = ( - 33CC10F02044A3C60003C045 /* AppDelegate.swift */, - 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, - 33E51913231747F40026EE4D /* DebugProfile.entitlements */, - 33E51914231749380026EE4D /* Release.entitlements */, - 33CC11242044D66E0003C045 /* Resources */, - 33BA886A226E78AF003329D5 /* Configs */, - ); - path = Runner; - sourceTree = ""; - }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C80D4294CF70F00263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 331C80D1294CF70F00263BE5 /* Sources */, - 331C80D2294CF70F00263BE5 /* Frameworks */, - 331C80D3294CF70F00263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C80DA294CF71000263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 33CC10EC2044A3C60003C045 /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 33CC10E92044A3C60003C045 /* Sources */, - 33CC10EA2044A3C60003C045 /* Frameworks */, - 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, - 3399D490228B24CF009A79C7 /* ShellScript */, - ); - buildRules = ( - ); - dependencies = ( - 33CC11202044C79F0003C045 /* PBXTargetDependency */, - ); - name = Runner; - productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* myedu.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 33CC10E52044A3C60003C045 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C80D4294CF70F00263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 33CC10EC2044A3C60003C045; - }; - 33CC10EC2044A3C60003C045 = { - CreatedOnToolsVersion = 9.2; - LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; - SystemCapabilities = { - com.apple.Sandbox = { - enabled = 1; - }; - }; - }; - 33CC111A2044C6BA0003C045 = { - CreatedOnToolsVersion = 9.2; - ProvisioningStyle = Manual; - }; - }; - }; - buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 33CC10E42044A3C60003C045; - productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 33CC10EC2044A3C60003C045 /* Runner */, - 331C80D4294CF70F00263BE5 /* RunnerTests */, - 33CC111A2044C6BA0003C045 /* Flutter Assemble */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C80D3294CF70F00263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10EB2044A3C60003C045 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, - 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3399D490228B24CF009A79C7 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; - }; - 33CC111E2044C6BF0003C045 /* ShellScript */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - Flutter/ephemeral/FlutterInputs.xcfilelist, - ); - inputPaths = ( - Flutter/ephemeral/tripwire, - ); - outputFileListPaths = ( - Flutter/ephemeral/FlutterOutputs.xcfilelist, - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C80D1294CF70F00263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 33CC10E92044A3C60003C045 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, - 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, - 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC10EC2044A3C60003C045 /* Runner */; - targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; - }; - 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; - targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { - isa = PBXVariantGroup; - children = ( - 33CC10F52044A3C60003C045 /* Base */, - ); - name = MainMenu.xib; - path = Runner; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 331C80DB294CF71000263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myedu.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/myedu"; - }; - name = Debug; - }; - 331C80DC294CF71000263BE5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myedu.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/myedu"; - }; - name = Release; - }; - 331C80DD294CF71000263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myedu.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/myedu"; - }; - name = Profile; - }; - 338D0CE9231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - 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_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Profile; - }; - 338D0CEA231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Profile; - }; - 338D0CEB231458BD00FA5F75 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Profile; - }; - 33CC10F92044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - 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_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 33CC10FA2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - 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_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - 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_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CODE_SIGN_IDENTITY = "-"; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 33CC10FC2044A3C60003C045 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 33CC10FD2044A3C60003C045 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - 33CC111C2044C6BA0003C045 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Manual; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 33CC111D2044C6BA0003C045 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C80DB294CF71000263BE5 /* Debug */, - 331C80DC294CF71000263BE5 /* Release */, - 331C80DD294CF71000263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10F92044A3C60003C045 /* Debug */, - 33CC10FA2044A3C60003C045 /* Release */, - 338D0CE9231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC10FC2044A3C60003C045 /* Debug */, - 33CC10FD2044A3C60003C045 /* Release */, - 338D0CEA231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 33CC111C2044C6BA0003C045 /* Debug */, - 33CC111D2044C6BA0003C045 /* Release */, - 338D0CEB231458BD00FA5F75 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 33CC10E52044A3C60003C045 /* Project object */; -} +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* myedu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "myedu.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* myedu.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* myedu.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myedu.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/myedu"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myedu.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/myedu"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/myedu.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/myedu"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 18d9810..fc6bf80 100644 --- a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 0d3025e..5ac413d 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,99 +1,99 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..59c6d39 100644 --- a/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 18d9810..fc6bf80 100644 --- a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,8 +1,8 @@ - - - - - IDEDidComputeMac32BitWarning - - - + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index b3c1761..c5c474d 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,13 +1,13 @@ -import Cocoa -import FlutterMacOS - -@main -class AppDelegate: FlutterAppDelegate { - override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { - return true - } - - override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { - return true - } -} +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f..8d4e7cb 100644 --- a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,68 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib index 80e867a..4632c69 100644 --- a/macos/Runner/Base.lproj/MainMenu.xib +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -1,343 +1,343 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 0c03dc4..f676c29 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -1,14 +1,14 @@ -// Application-level settings for the Runner target. -// -// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the -// future. If not, the values below would default to using the project name when this becomes a -// 'flutter create' template. - -// The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = myedu - -// The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu - -// The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright ยฉ 2025 com.example. All rights reserved. +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = myedu + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.myedu + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright ยฉ 2025 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig index 36b0fd9..b398823 100644 --- a/macos/Runner/Configs/Debug.xcconfig +++ b/macos/Runner/Configs/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "../../Flutter/Flutter-Debug.xcconfig" -#include "Warnings.xcconfig" +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig index dff4f49..d93e5dc 100644 --- a/macos/Runner/Configs/Release.xcconfig +++ b/macos/Runner/Configs/Release.xcconfig @@ -1,2 +1,2 @@ -#include "../../Flutter/Flutter-Release.xcconfig" -#include "Warnings.xcconfig" +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig index 42bcbf4..fb4d7d3 100644 --- a/macos/Runner/Configs/Warnings.xcconfig +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -1,13 +1,13 @@ -WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings -GCC_WARN_UNDECLARED_SELECTOR = YES -CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES -CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES -CLANG_WARN_PRAGMA_PACK = YES -CLANG_WARN_STRICT_PROTOTYPES = YES -CLANG_WARN_COMMA = YES -GCC_WARN_STRICT_SELECTOR_MATCH = YES -CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES -CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES -GCC_WARN_SHADOW = YES -CLANG_WARN_UNREACHABLE_CODE = YES +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements index dddb8a3..51d0967 100644 --- a/macos/Runner/DebugProfile.entitlements +++ b/macos/Runner/DebugProfile.entitlements @@ -1,12 +1,12 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.network.server - - - + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 4789daa..3733c1a 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -1,32 +1,32 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 3cc05eb..ab30cba 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -1,15 +1,15 @@ -import Cocoa -import FlutterMacOS - -class MainFlutterWindow: NSWindow { - override func awakeFromNib() { - let flutterViewController = FlutterViewController() - let windowFrame = self.frame - self.contentViewController = flutterViewController - self.setFrame(windowFrame, display: true) - - RegisterGeneratedPlugins(registry: flutterViewController) - - super.awakeFromNib() - } -} +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a..04336df 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -1,8 +1,8 @@ - - - - - com.apple.security.app-sandbox - - - + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift index 61f3bd1..21fe1ab 100644 --- a/macos/RunnerTests/RunnerTests.swift +++ b/macos/RunnerTests/RunnerTests.swift @@ -1,12 +1,12 @@ -import Cocoa -import FlutterMacOS -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. - } - -} +import Cocoa +import FlutterMacOS +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/myedu-functions/index.js b/myedu-functions/index.js index 2729b9f..bd60d6b 100644 --- a/myedu-functions/index.js +++ b/myedu-functions/index.js @@ -1,63 +1,63 @@ -const functions = require("firebase-functions"); -const admin = require("firebase-admin"); - -admin.initializeApp(); -const firestore = admin.firestore(); - -// Topic name for push notifications -const TOPIC = "pn"; - -// Trigger for notices collection -exports.onNewNoticeCreated = functions.firestore - .document("notices/{noticeId}") - .onCreate(async (snap, context) => { - const newDoc = snap.data(); - const title = newDoc.title || "New Notice"; - const content = newDoc.content || ""; - - // Send push notification - await admin.messaging().send({ - topic: TOPIC, - notification: { - title: title, - body: content, - }, - }); - - // Save to notifications collection - await firestore.collection("notifications").add({ - title: title, - content: content, - timestamp: admin.firestore.FieldValue.serverTimestamp(), - type: "notice", - }); - }); - -// Trigger for academic_fees collection -exports.onNewAcademicFeeCreated = functions.firestore - .document("academic_fees/{feeId}") - .onCreate(async (snap, context) => { - const newDoc = snap.data(); - const purpose = newDoc.purpose || "Academic Fee"; - const total = newDoc.total || 0; - - const title = `${purpose} - ${total} TK`; - const content = "You have a new bill ready"; - - // Send push notification - await admin.messaging().send({ - topic: TOPIC, - notification: { - title: title, - body: content, - }, - }); - - // Save to notifications collection - await firestore.collection("notifications").add({ - title: title, - content: content, - timestamp: admin.firestore.FieldValue.serverTimestamp(), - type: "academic_fee", - }); - }); +const functions = require("firebase-functions"); +const admin = require("firebase-admin"); + +admin.initializeApp(); +const firestore = admin.firestore(); + +// Topic name for push notifications +const TOPIC = "pn"; + +// Trigger for notices collection +exports.onNewNoticeCreated = functions.firestore + .document("notices/{noticeId}") + .onCreate(async (snap, context) => { + const newDoc = snap.data(); + const title = newDoc.title || "New Notice"; + const content = newDoc.content || ""; + + // Send push notification + await admin.messaging().send({ + topic: TOPIC, + notification: { + title: title, + body: content, + }, + }); + + // Save to notifications collection + await firestore.collection("notifications").add({ + title: title, + content: content, + timestamp: admin.firestore.FieldValue.serverTimestamp(), + type: "notice", + }); + }); + +// Trigger for academic_fees collection +exports.onNewAcademicFeeCreated = functions.firestore + .document("academic_fees/{feeId}") + .onCreate(async (snap, context) => { + const newDoc = snap.data(); + const purpose = newDoc.purpose || "Academic Fee"; + const total = newDoc.total || 0; + + const title = `${purpose} - ${total} TK`; + const content = "You have a new bill ready"; + + // Send push notification + await admin.messaging().send({ + topic: TOPIC, + notification: { + title: title, + body: content, + }, + }); + + // Save to notifications collection + await firestore.collection("notifications").add({ + title: title, + content: content, + timestamp: admin.firestore.FieldValue.serverTimestamp(), + type: "academic_fee", + }); + }); diff --git a/myedu-functions/package-lock.json b/myedu-functions/package-lock.json index 4d1b834..1e5a825 100644 --- a/myedu-functions/package-lock.json +++ b/myedu-functions/package-lock.json @@ -1,6910 +1,6910 @@ -{ - "name": "functions", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "functions", - "dependencies": { - "firebase-admin": "^11.8.0", - "firebase-functions": "^4.3.1" - }, - "devDependencies": { - "firebase-functions-test": "^3.1.0" - }, - "engines": { - "node": "18" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", - "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", - "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helpers": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@babel/generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", - "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/parser": "^7.27.1", - "@babel/types": "^7.27.1", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", - "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@fastify/busboy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", - "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", - "license": "MIT", - "dependencies": { - "text-decoding": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@firebase/app-types": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", - "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/auth-interop-types": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", - "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==", - "license": "Apache-2.0" - }, - "node_modules/@firebase/component": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", - "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database": { - "version": "0.14.4", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", - "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/auth-interop-types": "0.2.1", - "@firebase/component": "0.6.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "faye-websocket": "0.11.4", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-compat": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", - "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/component": "0.6.4", - "@firebase/database": "0.14.4", - "@firebase/database-types": "0.10.4", - "@firebase/logger": "0.4.0", - "@firebase/util": "1.9.3", - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/database-types": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", - "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", - "license": "Apache-2.0", - "dependencies": { - "@firebase/app-types": "0.9.0", - "@firebase/util": "1.9.3" - } - }, - "node_modules/@firebase/logger": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", - "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@firebase/util": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", - "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/@google-cloud/firestore": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.8.0.tgz", - "integrity": "sha512-JRpk06SmZXLGz0pNx1x7yU3YhkUXheKgH5hbDZ4kMsdhtfV5qPLJLRI4wv69K0cZorIk+zTMOwptue7hizo0eA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "functional-red-black-tree": "^1.0.1", - "google-gax": "^3.5.7", - "protobufjs": "^7.2.5" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/paginator": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", - "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/storage": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.12.0.tgz", - "integrity": "sha512-78nNAY7iiZ4O/BouWMWTD/oSF2YtYgYB3GZirn0To6eBOugjXVoK+GXgUXOl+HlqbAOyHxAVXOlsj3snfbQ1dw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", - "abort-controller": "^3.0.0", - "async-retry": "^1.3.3", - "compressible": "^2.0.12", - "duplexify": "^4.0.0", - "ent": "^2.2.0", - "extend": "^3.0.2", - "fast-xml-parser": "^4.2.2", - "gaxios": "^5.0.0", - "google-auth-library": "^8.0.1", - "mime": "^3.0.0", - "mime-types": "^2.0.8", - "p-limit": "^3.0.1", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0", - "uuid": "^8.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/storage/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.8.22", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.22.tgz", - "integrity": "sha512-oAjDdN7fzbUi+4hZjKG96MR6KTEubAeMpQEb+77qy+3r0Ua5xTFuie6JOLr4ZZgl5g+W5/uRTS2M1V8mVAFPuA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsdoc/salty": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", - "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v12.0.0" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.18", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz", - "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", - "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", - "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", - "license": "MIT", - "dependencies": { - "@types/ms": "*", - "@types/node": "*" - } - }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/lodash": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", - "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/markdown-it": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "license": "MIT", - "optional": true - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.15.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", - "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" - }, - "node_modules/@types/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/glob": "*", - "@types/node": "*" - } - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "optional": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "license": "MIT", - "optional": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "license": "MIT", - "optional": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async-retry": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", - "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", - "license": "MIT", - "optional": true, - "dependencies": { - "retry": "0.13.1" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, - "node_modules/bignumber.js": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", - "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", - "license": "MIT", - "optional": true, - "engines": { - "node": "*" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "license": "MIT", - "optional": true - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001718", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", - "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0", - "peer": true - }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "license": "MIT", - "optional": true, - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "optional": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "license": "MIT", - "optional": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "license": "MIT", - "optional": true, - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.157", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", - "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/ent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", - "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "punycode": "^1.4.1", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "optional": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "optional": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "devOptional": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "optional": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "license": "BSD-2-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT", - "optional": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT", - "optional": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "license": "MIT", - "optional": true - }, - "node_modules/fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/fast-xml-parser": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", - "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "strnum": "^1.1.1" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/firebase-admin": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.11.1.tgz", - "integrity": "sha512-UyEbq+3u6jWzCYbUntv/HuJiTixwh36G1R9j0v71mSvGAx/YZEWEW7uSGLYxBYE6ckVRQoKMr40PYUEzrm/4dg==", - "license": "Apache-2.0", - "dependencies": { - "@fastify/busboy": "^1.2.1", - "@firebase/database-compat": "^0.3.4", - "@firebase/database-types": "^0.10.4", - "@types/node": ">=12.12.47", - "jsonwebtoken": "^9.0.0", - "jwks-rsa": "^3.0.1", - "node-forge": "^1.3.1", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@google-cloud/firestore": "^6.8.0", - "@google-cloud/storage": "^6.9.5" - } - }, - "node_modules/firebase-functions": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.9.0.tgz", - "integrity": "sha512-IqxOEsVAWGcRv9KRGzWQR5mOFuNsil3vsfkRPPiaV1U/ATC27/jbahh4z8I4rW8Xqa6cQE5xqnw0ueyMH7i7Ag==", - "license": "MIT", - "dependencies": { - "@types/cors": "^2.8.5", - "@types/express": "4.17.3", - "cors": "^2.8.5", - "express": "^4.17.1", - "protobufjs": "^7.2.2" - }, - "bin": { - "firebase-functions": "lib/bin/firebase-functions.js" - }, - "engines": { - "node": ">=14.10.0" - }, - "peerDependencies": { - "firebase-admin": "^10.0.0 || ^11.0.0 || ^12.0.0" - } - }, - "node_modules/firebase-functions-test": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.4.1.tgz", - "integrity": "sha512-qAq0oszrBGdf4bnCF6t4FoSgMsepeIXh0Pi/FhikSE6e+TvKKGpfrfUP/5pFjJZxFcLsweoau88KydCql4xSeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/lodash": "^4.14.104", - "lodash": "^4.17.5", - "ts-deepmerge": "^2.0.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", - "firebase-functions": ">=4.9.0", - "jest": ">=28.0.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "license": "MIT", - "optional": true - }, - "node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/google-auth-library": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", - "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.3.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-gax": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", - "integrity": "sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@grpc/grpc-js": "~1.8.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "@types/rimraf": "^3.0.2", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.2.4", - "protobufjs-cli": "1.1.1", - "retry-request": "^5.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js", - "minifyProtoJson": "build/tools/minify.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-gax/node_modules/protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "deprecated": "Package is no longer maintained", - "license": "MIT", - "optional": true, - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "optional": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "devOptional": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", - "license": "MIT", - "optional": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jose": { - "version": "4.15.9", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", - "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, - "node_modules/jsdoc": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", - "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@babel/parser": "^7.20.15", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^14.1.1", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "license": "MIT", - "optional": true, - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jwks-rsa": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", - "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", - "license": "MIT", - "dependencies": { - "@types/express": "^4.17.20", - "@types/jsonwebtoken": "^9.0.4", - "debug": "^4.3.4", - "jose": "^4.15.4", - "limiter": "^1.1.5", - "lru-memoizer": "^2.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/jwks-rsa/node_modules/@types/express": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", - "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/jwks-rsa/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/jwks-rsa/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "license": "MIT", - "optional": true, - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "license": "MIT", - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "license": "MIT", - "optional": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/limiter": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", - "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT", - "optional": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lru-memoizer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", - "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", - "license": "MIT", - "dependencies": { - "lodash.clonedeep": "^4.5.0", - "lru-cache": "6.0.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "license": "MIT", - "optional": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it-anchor": { - "version": "8.6.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", - "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", - "license": "Unlicense", - "optional": true, - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" - } - }, - "node_modules/markdown-it/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0", - "optional": true - }, - "node_modules/marked": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", - "license": "MIT", - "optional": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "license": "MIT", - "optional": true - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "license": "MIT", - "optional": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "optional": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proto3-json-serializer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", - "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "protobufjs": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/protobufjs-cli": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", - "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^4.0.0", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/protobufjs-cli/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs-cli/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", - "license": "MIT", - "optional": true - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", - "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", - "license": "MIT", - "optional": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/retry-request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", - "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/retry-request/node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/retry-request/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "optional": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC", - "peer": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-events": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", - "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", - "license": "MIT", - "optional": true, - "dependencies": { - "stubs": "^3.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "license": "MIT", - "optional": true - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "optional": true - }, - "node_modules/stubs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", - "license": "MIT", - "optional": true - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/teeny-request": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", - "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-decoding": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", - "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==", - "license": "MIT" - }, - "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "optional": true - }, - "node_modules/ts-deepmerge": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz", - "integrity": "sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==", - "dev": true, - "license": "ISC" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "license": "MIT", - "optional": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT", - "optional": true - }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", - "license": "MIT", - "optional": true - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT", - "optional": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "optional": true - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "peer": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", - "license": "Apache-2.0", - "optional": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} +{ + "name": "functions", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "functions", + "dependencies": { + "firebase-admin": "^11.8.0", + "firebase-functions": "^4.3.1" + }, + "devDependencies": { + "firebase-functions-test": "^3.1.0" + }, + "engines": { + "node": "18" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "license": "MIT", + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.8.0.tgz", + "integrity": "sha512-JRpk06SmZXLGz0pNx1x7yU3YhkUXheKgH5hbDZ4kMsdhtfV5qPLJLRI4wv69K0cZorIk+zTMOwptue7hizo0eA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^3.5.7", + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.12.0.tgz", + "integrity": "sha512-78nNAY7iiZ4O/BouWMWTD/oSF2YtYgYB3GZirn0To6eBOugjXVoK+GXgUXOl+HlqbAOyHxAVXOlsj3snfbQ1dw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "fast-xml-parser": "^4.2.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.22", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.22.tgz", + "integrity": "sha512-oAjDdN7fzbUi+4hZjKG96MR6KTEubAeMpQEb+77qy+3r0Ua5xTFuie6JOLr4ZZgl5g+W5/uRTS2M1V8mVAFPuA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.18", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz", + "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", + "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/lodash": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", + "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "license": "MIT", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/bignumber.js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", + "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "license": "MIT", + "optional": true + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001718", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz", + "integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "license": "MIT", + "optional": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "license": "MIT", + "optional": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.157", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz", + "integrity": "sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "devOptional": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/firebase-admin": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.11.1.tgz", + "integrity": "sha512-UyEbq+3u6jWzCYbUntv/HuJiTixwh36G1R9j0v71mSvGAx/YZEWEW7uSGLYxBYE6ckVRQoKMr40PYUEzrm/4dg==", + "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^1.2.1", + "@firebase/database-compat": "^0.3.4", + "@firebase/database-types": "^0.10.4", + "@types/node": ">=12.12.47", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", + "node-forge": "^1.3.1", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^6.8.0", + "@google-cloud/storage": "^6.9.5" + } + }, + "node_modules/firebase-functions": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.9.0.tgz", + "integrity": "sha512-IqxOEsVAWGcRv9KRGzWQR5mOFuNsil3vsfkRPPiaV1U/ATC27/jbahh4z8I4rW8Xqa6cQE5xqnw0ueyMH7i7Ag==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "protobufjs": "^7.2.2" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^10.0.0 || ^11.0.0 || ^12.0.0" + } + }, + "node_modules/firebase-functions-test": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.4.1.tgz", + "integrity": "sha512-qAq0oszrBGdf4bnCF6t4FoSgMsepeIXh0Pi/FhikSE6e+TvKKGpfrfUP/5pFjJZxFcLsweoau88KydCql4xSeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5", + "ts-deepmerge": "^2.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "firebase-functions": ">=4.9.0", + "jest": ">=28.0.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", + "optional": true + }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", + "integrity": "sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "~1.8.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "@types/rimraf": "^3.0.2", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^8.0.2", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.2.4", + "protobufjs-cli": "1.1.1", + "retry-request": "^5.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax/node_modules/protobufjs": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "deprecated": "Package is no longer maintained", + "license": "MIT", + "optional": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "devOptional": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "license": "MIT", + "optional": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/jwks-rsa/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/jwks-rsa/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "license": "MIT", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "license": "MIT", + "optional": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "license": "Unlicense", + "optional": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "optional": true + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "license": "MIT", + "optional": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT", + "optional": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "license": "MIT", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto3-json-serializer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", + "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", + "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs-cli/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs-cli/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT", + "optional": true + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "license": "MIT", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/retry-request/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/retry-request/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", + "optional": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", + "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, + "node_modules/ts-deepmerge": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz", + "integrity": "sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==", + "dev": true, + "license": "ISC" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT", + "optional": true + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT", + "optional": true + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/myedu-functions/package.json b/myedu-functions/package.json index 392196b..92288f1 100644 --- a/myedu-functions/package.json +++ b/myedu-functions/package.json @@ -6,7 +6,8 @@ "shell": "firebase functions:shell", "start": "npm run shell", "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" + "logs": "firebase functions:log", + "seed:results": "node scripts/seed-results.js" }, "engines": { "node": "18" diff --git a/myedu-functions/scripts/seed-results.js b/myedu-functions/scripts/seed-results.js new file mode 100644 index 0000000..a82200a --- /dev/null +++ b/myedu-functions/scripts/seed-results.js @@ -0,0 +1,158 @@ +const admin = require("firebase-admin"); + +const args = process.argv.slice(2); + +function readArg(name, fallback) { + const prefix = `--${name}=`; + const match = args.find((arg) => arg.startsWith(prefix)); + return match ? match.slice(prefix.length).trim() : fallback; +} + +const studentId = readArg("studentId", process.env.STUDENT_ID || "2002010"); +const studentName = readArg( + "studentName", + process.env.STUDENT_NAME || "Demo Student" +); +const department = readArg( + "department", + process.env.DEPARTMENT || "Computer Science & Engineering" +); +const projectId = + readArg("projectId", process.env.GOOGLE_CLOUD_PROJECT) || + process.env.GCLOUD_PROJECT || + "my-edu-23fce"; + +admin.initializeApp({ projectId }); + +const db = admin.firestore(); + +const semesterResults = [ + { + semester: "1st", + academicYear: "2020-21", + courses: [ + ["CSE-101", "Structured Programming", "3.0", "A", "4.0"], + ["CSE-102", "Structured Programming Lab", "1.5", "A+", "4.0"], + ["MAT-101", "Differential Calculus", "3.0", "A-", "3.7"], + ["PHY-101", "Physics", "3.0", "B+", "3.3"], + ["ENG-101", "Communicative English", "2.0", "A", "4.0"], + ], + }, + { + semester: "2nd", + academicYear: "2020-21", + courses: [ + ["CSE-201", "Object Oriented Programming", "3.0", "A", "4.0"], + ["CSE-202", "Object Oriented Programming Lab", "1.5", "A", "4.0"], + ["MAT-201", "Integral Calculus", "3.0", "A-", "3.7"], + ["EEE-201", "Electrical Circuits", "3.0", "B+", "3.3"], + ["HUM-201", "Bangladesh Studies", "2.0", "A", "4.0"], + ], + }, + { + semester: "3rd", + academicYear: "2021-22", + courses: [ + ["CSE-301", "Data Structures", "3.0", "A", "4.0"], + ["CSE-302", "Data Structures Lab", "1.5", "A+", "4.0"], + ["CSE-303", "Digital Logic Design", "3.0", "A-", "3.7"], + ["MAT-301", "Linear Algebra", "3.0", "B+", "3.3"], + ["STA-301", "Statistics", "3.0", "A-", "3.7"], + ], + }, + { + semester: "4th", + academicYear: "2021-22", + courses: [ + ["CSE-401", "Algorithms", "3.0", "A-", "3.7"], + ["CSE-402", "Algorithms Lab", "1.5", "A", "4.0"], + ["CSE-403", "Computer Architecture", "3.0", "B+", "3.3"], + ["CSE-404", "Database Systems", "3.0", "A", "4.0"], + ["MAT-401", "Discrete Mathematics", "3.0", "A-", "3.7"], + ], + }, + { + semester: "5th", + academicYear: "2022-23", + courses: [ + ["CSE-501", "Operating Systems", "3.0", "A", "4.0"], + ["CSE-502", "Operating Systems Lab", "1.5", "A", "4.0"], + ["CSE-503", "Computer Networks", "3.0", "B+", "3.3"], + ["CSE-504", "Software Engineering", "3.0", "A-", "3.7"], + ["CSE-505", "Microprocessors", "3.0", "B", "3.0"], + ], + }, + { + semester: "6th", + academicYear: "2022-23", + courses: [ + ["CSE-601", "Web Engineering", "3.0", "A", "4.0"], + ["CSE-602", "Web Engineering Lab", "1.5", "A+", "4.0"], + ["CSE-603", "Artificial Intelligence", "3.0", "A-", "3.7"], + ["CSE-604", "Compiler Design", "3.0", "B+", "3.3"], + ["CSE-605", "Numerical Methods", "3.0", "A", "4.0"], + ], + }, + { + semester: "7th", + academicYear: "2023-24", + courses: [ + ["CSE-701", "Machine Learning", "3.0", "A", "4.0"], + ["CSE-702", "Machine Learning Lab", "1.5", "A", "4.0"], + ["CSE-703", "Distributed Systems", "3.0", "A-", "3.7"], + ["CSE-704", "Information Security", "3.0", "B+", "3.3"], + ["CSE-705", "Project Management", "2.0", "A", "4.0"], + ], + }, + { + semester: "8th", + academicYear: "2023-24", + courses: [ + ["CSE-801", "Cloud Computing", "3.0", "A", "4.0"], + ["CSE-802", "Mobile App Development", "3.0", "A+", "4.0"], + ["CSE-803", "Data Mining", "3.0", "A-", "3.7"], + ["CSE-804", "Thesis / Project", "4.0", "A", "4.0"], + ["CSE-805", "Professional Ethics", "2.0", "A", "4.0"], + ], + }, +]; + +function toCourse([courseId, courseName, credit, grade, gpa]) { + return { courseId, courseName, credit, grade, gpa }; +} + +async function seedResults() { + if (!studentId) { + throw new Error("studentId is required. Use STUDENT_ID=your_id."); + } + + const batch = db.batch(); + + semesterResults.forEach((result) => { + const docId = `${studentId}_${result.semester.toLowerCase()}_${result.academicYear}`; + const docRef = db.collection("results").doc(docId); + + batch.set(docRef, { + studentId, + studentName, + department, + semester: result.semester, + academicYear: result.academicYear, + courses: result.courses.map(toCourse), + seededAt: admin.firestore.FieldValue.serverTimestamp(), + }); + }); + + await batch.commit(); + + console.log( + `Seeded ${semesterResults.length} result documents for studentId=${studentId} in project=${projectId}` + ); +} + +seedResults() + .then(() => process.exit(0)) + .catch((error) => { + console.error("Failed to seed results:", error); + process.exit(1); + }); diff --git a/pubspec.lock b/pubspec.lock index 5f6aa7b..2c3453b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,1010 +1,1010 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _flutterfire_internals: - dependency: transitive - description: - name: _flutterfire_internals - sha256: "214e6f07e2a44f45972e0365c7b537eaeaddb4598db0778dd4ac64b4acd3f5b1" - url: "https://pub.dev" - source: hosted - version: "1.3.55" - archive: - dependency: transitive - description: - name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" - source: hosted - version: "4.0.7" - async: - dependency: transitive - description: - name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.dev" - source: hosted - version: "2.12.0" - awesome_notifications: - dependency: "direct main" - description: - name: awesome_notifications - sha256: "0d5fa4457f2ba4e536adc3ef6af709cdcecf4a05a1f3035981e9afa2f899b2a8" - url: "https://pub.dev" - source: hosted - version: "0.10.1" - barcode: - dependency: transitive - description: - name: barcode - sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" - url: "https://pub.dev" - source: hosted - version: "2.2.9" - bidi: - dependency: transitive - description: - name: bidi - sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" - url: "https://pub.dev" - source: hosted - version: "2.0.13" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - 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: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - clock: - dependency: transitive - description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" - source: hosted - version: "1.1.2" - cloud_firestore: - dependency: "direct main" - description: - name: cloud_firestore - sha256: d25c956be5261c14bc9a69c9662de8addb308376b4b53a64469aade52e7b02f8 - url: "https://pub.dev" - source: hosted - version: "5.6.8" - cloud_firestore_platform_interface: - dependency: transitive - description: - name: cloud_firestore_platform_interface - sha256: ee2b8f8c602ede36073afd3741e99cfea9dd982b4a44833daf665134d151c32a - url: "https://pub.dev" - source: hosted - version: "6.6.8" - cloud_firestore_web: - dependency: transitive - description: - name: cloud_firestore_web - sha256: b99bc4f1f70787f694b73bc6fce238d4d6cc822c9b31ba8ef1578b180b6f77bc - url: "https://pub.dev" - source: hosted - version: "4.4.8" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - cross_file: - dependency: transitive - description: - name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" - source: hosted - version: "0.3.4+2" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" - equatable: - dependency: transitive - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" - url: "https://pub.dev" - source: hosted - version: "1.3.2" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - file_selector_linux: - dependency: transitive - description: - name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" - url: "https://pub.dev" - source: hosted - version: "0.9.3+2" - file_selector_macos: - dependency: transitive - description: - name: file_selector_macos - sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" - url: "https://pub.dev" - source: hosted - version: "0.9.4+2" - file_selector_platform_interface: - dependency: transitive - description: - name: file_selector_platform_interface - sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b - url: "https://pub.dev" - source: hosted - version: "2.6.2" - file_selector_windows: - dependency: transitive - description: - name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" - url: "https://pub.dev" - source: hosted - version: "0.9.3+4" - firebase_auth: - dependency: "direct main" - description: - name: firebase_auth - sha256: "10cd3f00a247f33b0a5c77574011a87379432bf3fec77a500b55f2bcc30ddd8b" - url: "https://pub.dev" - source: hosted - version: "5.5.4" - firebase_auth_platform_interface: - dependency: transitive - description: - name: firebase_auth_platform_interface - sha256: "2d15872a8899b0459fab6b4c148fd142e135acfc8a303d383d80b455e4dba7bd" - url: "https://pub.dev" - source: hosted - version: "7.6.3" - firebase_auth_web: - dependency: transitive - description: - name: firebase_auth_web - sha256: efba45393050ca03d992eae1d305d5fc8c0c9f5980624053512e935c23767c4f - url: "https://pub.dev" - source: hosted - version: "5.14.3" - firebase_core: - dependency: "direct main" - description: - name: firebase_core - sha256: "8cfe3c900512399ce8d50fcc817e5758ff8615eeb6fa5c846a4cc47bbf6353b6" - url: "https://pub.dev" - source: hosted - version: "3.13.1" - firebase_core_platform_interface: - dependency: transitive - description: - name: firebase_core_platform_interface - sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf - url: "https://pub.dev" - source: hosted - version: "5.4.0" - firebase_core_web: - dependency: transitive - description: - name: firebase_core_web - sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e - url: "https://pub.dev" - source: hosted - version: "2.23.0" - firebase_messaging: - dependency: "direct main" - description: - name: firebase_messaging - sha256: "38111089e511f03daa2c66b4c3614c16421b7d78c84ee04331a0a65b47df4542" - url: "https://pub.dev" - source: hosted - version: "15.2.6" - firebase_messaging_platform_interface: - dependency: transitive - description: - name: firebase_messaging_platform_interface - sha256: ba254769982e5f439e534eed68856181c74e2b3f417c8188afad2bb440807cc7 - url: "https://pub.dev" - source: hosted - version: "4.6.6" - firebase_messaging_web: - dependency: transitive - description: - name: firebase_messaging_web - sha256: dba89137272aac39e95f71408ba25c33fb8ed903cd5fa8d1e49b5cd0d96069e0 - url: "https://pub.dev" - source: hosted - version: "3.10.6" - firebase_storage: - dependency: "direct main" - description: - name: firebase_storage - sha256: "4c1438da319c7599d974dcc4290ed2685a44f614355d1e9fbf516fcb404543b0" - url: "https://pub.dev" - source: hosted - version: "12.4.6" - firebase_storage_platform_interface: - dependency: transitive - description: - name: firebase_storage_platform_interface - sha256: "4be3b79079abb7708e041ed0178afb57ddb41cf0d77258c19a6befb057925e9d" - url: "https://pub.dev" - source: hosted - version: "5.2.6" - firebase_storage_web: - dependency: transitive - description: - name: firebase_storage_web - sha256: eb7890f7fcfcb47db9f9ed0c9260b99cfb5e3cc1cafd7dd9bfeea5471c148966 - url: "https://pub.dev" - source: hosted - version: "3.10.13" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - fl_chart: - dependency: "direct main" - description: - name: fl_chart - sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - flip_card: - dependency: "direct main" - description: - name: flip_card - sha256: "5d4aa58f3983cced0782f4ce45826b7eea36e8e464964d9209dcbc7a87b2292f" - url: "https://pub.dev" - source: hosted - version: "0.7.0" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" - url: "https://pub.dev" - source: hosted - version: "3.4.1" - flutter_easyloading: - dependency: "direct main" - description: - name: flutter_easyloading - sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c - url: "https://pub.dev" - source: hosted - version: "3.0.5" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e - url: "https://pub.dev" - source: hosted - version: "2.0.28" - flutter_spinkit: - dependency: transitive - description: - name: flutter_spinkit - sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 - url: "https://pub.dev" - source: hosted - version: "5.2.1" - flutter_sslcommerz: - dependency: "direct main" - description: - name: flutter_sslcommerz - sha256: "13cf2f0c0504619ac2f71848bce849d3557f14fe11f68058e4889ef927240945" - url: "https://pub.dev" - source: hosted - version: "2.5.1" - flutter_staggered_animations: - dependency: "direct main" - description: - name: flutter_staggered_animations - sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - 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" - get: - dependency: "direct main" - description: - name: get - sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 - url: "https://pub.dev" - source: hosted - version: "4.7.2" - http: - dependency: "direct main" - description: - name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" - url: "https://pub.dev" - source: hosted - version: "1.4.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - image: - dependency: transitive - description: - name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" - url: "https://pub.dev" - source: hosted - version: "4.5.4" - image_picker: - dependency: "direct main" - description: - name: image_picker - sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" - url: "https://pub.dev" - source: hosted - version: "1.1.2" - image_picker_android: - dependency: transitive - description: - name: image_picker_android - sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" - url: "https://pub.dev" - source: hosted - version: "0.8.12+23" - image_picker_for_web: - dependency: transitive - description: - name: image_picker_for_web - sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" - url: "https://pub.dev" - source: hosted - version: "3.0.6" - image_picker_ios: - dependency: transitive - description: - name: image_picker_ios - sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" - url: "https://pub.dev" - source: hosted - version: "0.8.12+2" - image_picker_linux: - dependency: transitive - description: - name: image_picker_linux - sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" - url: "https://pub.dev" - source: hosted - version: "0.2.1+2" - image_picker_macos: - dependency: transitive - description: - name: image_picker_macos - sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" - url: "https://pub.dev" - source: hosted - version: "0.2.1+2" - image_picker_platform_interface: - dependency: transitive - description: - name: image_picker_platform_interface - sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" - url: "https://pub.dev" - source: hosted - version: "2.10.1" - image_picker_windows: - dependency: transitive - description: - name: image_picker_windows - sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" - url: "https://pub.dev" - source: hosted - version: "0.2.1+1" - intl: - dependency: "direct main" - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec - url: "https://pub.dev" - source: hosted - version: "10.0.8" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" - source: hosted - version: "3.0.9" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" - source: hosted - version: "5.1.1" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - mailer: - dependency: "direct main" - description: - name: mailer - sha256: c6ae65c1d40cfe9c06fa50ff9be6c1cfb18cca60a7bb935e318163bd7f1f0d17 - url: "https://pub.dev" - source: hosted - version: "6.4.1" - matcher: - dependency: transitive - description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" - source: hosted - version: "0.12.17" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" - source: hosted - version: "1.16.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" - url: "https://pub.dev" - source: hosted - version: "2.1.0" - open_file: - dependency: "direct main" - description: - name: open_file - sha256: d17e2bddf5b278cb2ae18393d0496aa4f162142ba97d1a9e0c30d476adf99c0e - url: "https://pub.dev" - source: hosted - version: "3.5.10" - open_file_android: - dependency: transitive - description: - name: open_file_android - sha256: "58141fcaece2f453a9684509a7275f231ac0e3d6ceb9a5e6de310a7dff9084aa" - url: "https://pub.dev" - source: hosted - version: "1.0.6" - open_file_ios: - dependency: transitive - description: - name: open_file_ios - sha256: "02996f01e5f6863832068e97f8f3a5ef9b613516db6897f373b43b79849e4d07" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - open_file_linux: - dependency: transitive - description: - name: open_file_linux - sha256: d189f799eecbb139c97f8bc7d303f9e720954fa4e0fa1b0b7294767e5f2d7550 - url: "https://pub.dev" - source: hosted - version: "0.0.5" - open_file_mac: - dependency: transitive - description: - name: open_file_mac - sha256: "1440b1e37ceb0642208cfeb2c659c6cda27b25187a90635c9d1acb7d0584d324" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - open_file_platform_interface: - dependency: transitive - description: - name: open_file_platform_interface - sha256: "101b424ca359632699a7e1213e83d025722ab668b9fd1412338221bf9b0e5757" - url: "https://pub.dev" - source: hosted - version: "1.0.3" - open_file_web: - dependency: transitive - description: - name: open_file_web - sha256: e3dbc9584856283dcb30aef5720558b90f88036360bd078e494ab80a80130c4f - url: "https://pub.dev" - source: hosted - version: "0.0.4" - open_file_windows: - dependency: transitive - description: - name: open_file_windows - sha256: d26c31ddf935a94a1a3aa43a23f4fff8a5ff4eea395fe7a8cb819cf55431c875 - url: "https://pub.dev" - source: hosted - version: "0.0.3" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - 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: - name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" - source: hosted - version: "2.1.5" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 - url: "https://pub.dev" - source: hosted - version: "2.2.17" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" - source: hosted - version: "2.3.0" - pdf: - dependency: "direct main" - description: - name: pdf - sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" - url: "https://pub.dev" - source: hosted - version: "3.11.3" - pdf_widget_wrapper: - dependency: transitive - description: - name: pdf_widget_wrapper - sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5 - url: "https://pub.dev" - source: hosted - version: "1.0.4" - percent_indicator: - dependency: "direct main" - description: - name: percent_indicator - sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd" - url: "https://pub.dev" - source: hosted - version: "4.2.5" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.dev" - source: hosted - version: "6.1.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" - posix: - dependency: transitive - description: - name: posix - sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 - url: "https://pub.dev" - source: hosted - version: "6.0.2" - printing: - dependency: "direct main" - description: - name: printing - sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93" - url: "https://pub.dev" - source: hosted - version: "5.14.2" - qr: - dependency: transitive - description: - name: qr - sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - qr_flutter: - dependency: "direct main" - description: - name: qr_flutter - sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" - url: "https://pub.dev" - source: hosted - version: "4.1.0" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" - url: "https://pub.dev" - source: hosted - version: "0.28.0" - shimmer: - dependency: "direct main" - description: - name: shimmer - sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 - url: "https://pub.dev" - source: hosted - version: "2.4.2" - sqflite_android: - dependency: transitive - description: - name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" - url: "https://pub.dev" - source: hosted - version: "2.4.1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" - url: "https://pub.dev" - source: hosted - version: "2.5.5" - sqflite_darwin: - dependency: transitive - description: - name: sqflite_darwin - sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" - url: "https://pub.dev" - source: hosted - version: "2.4.2" - sqflite_platform_interface: - dependency: transitive - description: - name: sqflite_platform_interface - sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" - url: "https://pub.dev" - source: hosted - version: "2.4.0" - 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" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" - url: "https://pub.dev" - source: hosted - version: "3.3.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: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" - source: hosted - version: "0.7.4" - timeago: - dependency: "direct main" - description: - name: timeago - sha256: b05159406a97e1cbb2b9ee4faa9fb096fe0e2dfcd8b08fcd2a00553450d3422e - url: "https://pub.dev" - source: hosted - version: "3.7.1" - 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: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" - source: hosted - version: "4.5.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.dev" - source: hosted - version: "14.3.1" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" -sdks: - dart: ">=3.7.2 <4.0.0" - flutter: ">=3.27.4" +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "214e6f07e2a44f45972e0365c7b537eaeaddb4598db0778dd4ac64b4acd3f5b1" + url: "https://pub.dev" + source: hosted + version: "1.3.55" + archive: + dependency: transitive + description: + name: archive + sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + url: "https://pub.dev" + source: hosted + version: "4.0.7" + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + awesome_notifications: + dependency: "direct main" + description: + name: awesome_notifications + sha256: "0d5fa4457f2ba4e536adc3ef6af709cdcecf4a05a1f3035981e9afa2f899b2a8" + url: "https://pub.dev" + source: hosted + version: "0.10.1" + barcode: + dependency: transitive + description: + name: barcode + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + bidi: + dependency: transitive + description: + name: bidi + sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" + url: "https://pub.dev" + source: hosted + version: "2.0.13" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + 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: + 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" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: d25c956be5261c14bc9a69c9662de8addb308376b4b53a64469aade52e7b02f8 + url: "https://pub.dev" + source: hosted + version: "5.6.8" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: ee2b8f8c602ede36073afd3741e99cfea9dd982b4a44833daf665134d151c32a + url: "https://pub.dev" + source: hosted + version: "6.6.8" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: b99bc4f1f70787f694b73bc6fce238d4d6cc822c9b31ba8ef1578b180b6f77bc + url: "https://pub.dev" + source: hosted + version: "4.4.8" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "10cd3f00a247f33b0a5c77574011a87379432bf3fec77a500b55f2bcc30ddd8b" + url: "https://pub.dev" + source: hosted + version: "5.5.4" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "2d15872a8899b0459fab6b4c148fd142e135acfc8a303d383d80b455e4dba7bd" + url: "https://pub.dev" + source: hosted + version: "7.6.3" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: efba45393050ca03d992eae1d305d5fc8c0c9f5980624053512e935c23767c4f + url: "https://pub.dev" + source: hosted + version: "5.14.3" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "8cfe3c900512399ce8d50fcc817e5758ff8615eeb6fa5c846a4cc47bbf6353b6" + url: "https://pub.dev" + source: hosted + version: "3.13.1" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf + url: "https://pub.dev" + source: hosted + version: "5.4.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e + url: "https://pub.dev" + source: hosted + version: "2.23.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "38111089e511f03daa2c66b4c3614c16421b7d78c84ee04331a0a65b47df4542" + url: "https://pub.dev" + source: hosted + version: "15.2.6" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: ba254769982e5f439e534eed68856181c74e2b3f417c8188afad2bb440807cc7 + url: "https://pub.dev" + source: hosted + version: "4.6.6" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: dba89137272aac39e95f71408ba25c33fb8ed903cd5fa8d1e49b5cd0d96069e0 + url: "https://pub.dev" + source: hosted + version: "3.10.6" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + sha256: "4c1438da319c7599d974dcc4290ed2685a44f614355d1e9fbf516fcb404543b0" + url: "https://pub.dev" + source: hosted + version: "12.4.6" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: "4be3b79079abb7708e041ed0178afb57ddb41cf0d77258c19a6befb057925e9d" + url: "https://pub.dev" + source: hosted + version: "5.2.6" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: eb7890f7fcfcb47db9f9ed0c9260b99cfb5e3cc1cafd7dd9bfeea5471c148966 + url: "https://pub.dev" + source: hosted + version: "3.10.13" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flip_card: + dependency: "direct main" + description: + name: flip_card + sha256: "5d4aa58f3983cced0782f4ce45826b7eea36e8e464964d9209dcbc7a87b2292f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_easyloading: + dependency: "direct main" + description: + name: flutter_easyloading + sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c + url: "https://pub.dev" + source: hosted + version: "3.0.5" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" + flutter_spinkit: + dependency: transitive + description: + name: flutter_spinkit + sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 + url: "https://pub.dev" + source: hosted + version: "5.2.1" + flutter_sslcommerz: + dependency: "direct main" + description: + name: flutter_sslcommerz + sha256: "13cf2f0c0504619ac2f71848bce849d3557f14fe11f68058e4889ef927240945" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + flutter_staggered_animations: + dependency: "direct main" + description: + name: flutter_staggered_animations + sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + 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" + get: + dependency: "direct main" + description: + name: get + sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425 + url: "https://pub.dev" + source: hosted + version: "4.7.2" + http: + dependency: "direct main" + description: + name: http + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + url: "https://pub.dev" + source: hosted + version: "4.5.4" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.dev" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.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: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + mailer: + dependency: "direct main" + description: + name: mailer + sha256: c6ae65c1d40cfe9c06fa50ff9be6c1cfb18cca60a7bb935e318163bd7f1f0d17 + url: "https://pub.dev" + source: hosted + version: "6.4.1" + 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" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + open_file: + dependency: "direct main" + description: + name: open_file + sha256: d17e2bddf5b278cb2ae18393d0496aa4f162142ba97d1a9e0c30d476adf99c0e + url: "https://pub.dev" + source: hosted + version: "3.5.10" + open_file_android: + dependency: transitive + description: + name: open_file_android + sha256: "58141fcaece2f453a9684509a7275f231ac0e3d6ceb9a5e6de310a7dff9084aa" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + open_file_ios: + dependency: transitive + description: + name: open_file_ios + sha256: "02996f01e5f6863832068e97f8f3a5ef9b613516db6897f373b43b79849e4d07" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + open_file_linux: + dependency: transitive + description: + name: open_file_linux + sha256: d189f799eecbb139c97f8bc7d303f9e720954fa4e0fa1b0b7294767e5f2d7550 + url: "https://pub.dev" + source: hosted + version: "0.0.5" + open_file_mac: + dependency: transitive + description: + name: open_file_mac + sha256: "1440b1e37ceb0642208cfeb2c659c6cda27b25187a90635c9d1acb7d0584d324" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + open_file_platform_interface: + dependency: transitive + description: + name: open_file_platform_interface + sha256: "101b424ca359632699a7e1213e83d025722ab668b9fd1412338221bf9b0e5757" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + open_file_web: + dependency: transitive + description: + name: open_file_web + sha256: e3dbc9584856283dcb30aef5720558b90f88036360bd078e494ab80a80130c4f + url: "https://pub.dev" + source: hosted + version: "0.0.4" + open_file_windows: + dependency: transitive + description: + name: open_file_windows + sha256: d26c31ddf935a94a1a3aa43a23f4fff8a5ff4eea395fe7a8cb819cf55431c875 + url: "https://pub.dev" + source: hosted + version: "0.0.3" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + 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: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" + url: "https://pub.dev" + source: hosted + version: "3.11.3" + pdf_widget_wrapper: + dependency: transitive + description: + name: pdf_widget_wrapper + sha256: c930860d987213a3d58c7ec3b7ecf8085c3897f773e8dc23da9cae60a5d6d0f5 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + percent_indicator: + dependency: "direct main" + description: + name: percent_indicator + sha256: "157d29133bbc6ecb11f923d36e7960a96a3f28837549a20b65e5135729f0f9fd" + url: "https://pub.dev" + source: hosted + version: "4.2.5" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.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" + posix: + dependency: transitive + description: + name: posix + sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + printing: + dependency: "direct main" + description: + name: printing + sha256: "482cd5a5196008f984bb43ed0e47cbfdca7373490b62f3b27b3299275bf22a93" + url: "https://pub.dev" + source: hosted + version: "5.14.2" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + 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" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.dev" + source: hosted + version: "3.3.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" + timeago: + dependency: "direct main" + description: + name: timeago + sha256: b05159406a97e1cbb2b9ee4faa9fb096fe0e2dfcd8b08fcd2a00553450d3422e + url: "https://pub.dev" + source: hosted + version: "3.7.1" + 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: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + 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: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.dev" + source: hosted + version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" +sdks: + dart: ">=3.10.0-0 <4.0.0" + flutter: ">=3.27.4" diff --git a/pubspec.yaml b/pubspec.yaml index 58969b8..a140163 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,114 +1,114 @@ -name: myedu -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.7.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 - firebase_core: ^3.13.1 - cloud_firestore: ^5.6.8 - firebase_auth: ^5.5.4 - intl: ^0.20.2 - get: ^4.7.2 - image_picker: ^1.1.2 - cached_network_image: ^3.4.1 - firebase_storage: ^12.4.6 - percent_indicator: ^4.2.5 - fl_chart: ^1.0.0 - shimmer: ^3.0.0 - timeago: ^3.7.1 - flutter_staggered_animations: ^1.1.1 - flutter_easyloading: ^3.0.5 - flip_card: ^0.7.0 - qr_flutter: ^4.1.0 - pdf: ^3.11.0 - printing: ^5.12.0 - path_provider: ^2.1.2 - open_file: ^3.3.2 - firebase_messaging: ^15.2.6 - awesome_notifications: ^0.10.1 - flutter_sslcommerz: ^2.5.1 - mailer: ^6.0.1 - http: ^1.4.0 - -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: ^5.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 +name: myedu +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.7.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 + firebase_core: ^3.13.1 + cloud_firestore: ^5.6.8 + firebase_auth: ^5.5.4 + intl: ^0.20.2 + get: ^4.7.2 + image_picker: ^1.1.2 + cached_network_image: ^3.4.1 + firebase_storage: ^12.4.6 + percent_indicator: ^4.2.5 + fl_chart: ^1.0.0 + shimmer: ^3.0.0 + timeago: ^3.7.1 + flutter_staggered_animations: ^1.1.1 + flutter_easyloading: ^3.0.5 + flip_card: ^0.7.0 + qr_flutter: ^4.1.0 + pdf: ^3.11.0 + printing: ^5.12.0 + path_provider: ^2.1.2 + open_file: ^3.3.2 + firebase_messaging: ^15.2.6 + awesome_notifications: ^0.10.1 + flutter_sslcommerz: ^2.5.1 + mailer: ^6.0.1 + http: ^1.4.0 + +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: ^5.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/test/email_service_test.dart b/test/email_service_test.dart index 44dfe76..6d00e9f 100644 --- a/test/email_service_test.dart +++ b/test/email_service_test.dart @@ -1,21 +1,21 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:myedu/services/email_service.dart'; - -void main() { - group('EmailService Tests', () { - test('should generate valid OTP', () { - final emailService = EmailService(); - - // Test that OTP generation works (this would be in AuthController) - // For now, just test that the service can be instantiated - expect(emailService, isA()); - }); - - test('should have correct email credentials', () { - final emailService = EmailService(); - - expect(emailService.username, 'tanvirrobin0@gmail.com'); - expect(emailService.password, '4uhurwkwebgj'); - }); - }); -} +import 'package:flutter_test/flutter_test.dart'; +import 'package:myedu/services/email_service.dart'; + +void main() { + group('EmailService Tests', () { + test('should generate valid OTP', () { + final emailService = EmailService(); + + // Test that OTP generation works (this would be in AuthController) + // For now, just test that the service can be instantiated + expect(emailService, isA()); + }); + + test('should have correct email credentials', () { + final emailService = EmailService(); + + expect(emailService.username, 'tanvirrobin0@gmail.com'); + expect(emailService.password, '4uhurwkwebgj'); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart index a590d6a..ef0a7bf 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +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:myedu/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); - }); -} +// 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:myedu/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); + }); +} diff --git a/web/index.html b/web/index.html index 10d6b45..9cc581a 100644 --- a/web/index.html +++ b/web/index.html @@ -1,38 +1,38 @@ - - - - - - - - - - - - - - - - - - - - myedu - - - - - - + + + + + + + + + + + + + + + + + + + + myedu + + + + + + diff --git a/web/manifest.json b/web/manifest.json index cadeea2..05a7d0e 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -1,35 +1,35 @@ -{ - "name": "myedu", - "short_name": "myedu", - "start_url": ".", - "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A new Flutter project.", - "orientation": "portrait-primary", - "prefer_related_applications": false, - "icons": [ - { - "src": "icons/Icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icons/Icon-512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "icons/Icon-maskable-192.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "icons/Icon-maskable-512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" - } - ] -} +{ + "name": "myedu", + "short_name": "myedu", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore index d492d0d..ec4098a 100644 --- a/windows/.gitignore +++ b/windows/.gitignore @@ -1,17 +1,17 @@ -flutter/ephemeral/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index da81beb..2c7022a 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -1,108 +1,108 @@ -# Project-level configuration. -cmake_minimum_required(VERSION 3.14) -project(myedu LANGUAGES CXX) - -# The name of the executable created for the application. Change this to change -# the on-disk name of your application. -set(BINARY_NAME "myedu") - -# Explicitly opt in to modern CMake behaviors to avoid warnings with recent -# versions of CMake. -cmake_policy(VERSION 3.14...3.25) - -# Define build configuration option. -get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(IS_MULTICONFIG) - set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" - CACHE STRING "" FORCE) -else() - if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - set(CMAKE_BUILD_TYPE "Debug" CACHE - STRING "Flutter build mode" FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS - "Debug" "Profile" "Release") - endif() -endif() -# Define settings for the Profile build mode. -set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") -set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") - -# Use Unicode for all projects. -add_definitions(-DUNICODE -D_UNICODE) - -# Compilation settings that should be applied to most targets. -# -# Be cautious about adding new options here, as plugins use this function by -# default. In most cases, you should add new options to specific targets instead -# of modifying this function. -function(APPLY_STANDARD_SETTINGS TARGET) - target_compile_features(${TARGET} PUBLIC cxx_std_17) - target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") - target_compile_options(${TARGET} PRIVATE /EHsc) - target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") - target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") -endfunction() - -# Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") -add_subdirectory(${FLUTTER_MANAGED_DIR}) - -# Application build; see runner/CMakeLists.txt. -add_subdirectory("runner") - - -# Generated plugin build rules, which manage building the plugins and adding -# them to the application. -include(flutter/generated_plugins.cmake) - - -# === Installation === -# Support files are copied into place next to the executable, so that it can -# run in place. This is done instead of making a separate bundle (as on Linux) -# so that building and running from within Visual Studio will work. -set(BUILD_BUNDLE_DIR "$") -# Make the "install" step default, as it's required to run. -set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) -if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) -endif() - -set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") -set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") - -install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - COMPONENT Runtime) - -install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -if(PLUGIN_BUNDLED_LIBRARIES) - install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -endif() - -# Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) - -# Fully re-copy the assets directory on each build to avoid having stale files -# from a previous install. -set(FLUTTER_ASSET_DIR_NAME "flutter_assets") -install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") - " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) - -# Install the AOT library on non-Debug builds only. -install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" - CONFIGURATIONS Profile;Release - COMPONENT Runtime) +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(myedu LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "myedu") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt index 903f489..efb62eb 100644 --- a/windows/flutter/CMakeLists.txt +++ b/windows/flutter/CMakeLists.txt @@ -1,109 +1,109 @@ -# This file controls Flutter-level build steps. It should not be edited. -cmake_minimum_required(VERSION 3.14) - -set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") - -# Configuration provided via flutter tool. -include(${EPHEMERAL_DIR}/generated_config.cmake) - -# TODO: Move the rest of this into files in ephemeral. See -# https://github.com/flutter/flutter/issues/57146. -set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") - -# Set fallback configurations for older versions of the flutter tool. -if (NOT DEFINED FLUTTER_TARGET_PLATFORM) - set(FLUTTER_TARGET_PLATFORM "windows-x64") -endif() - -# === Flutter Library === -set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") - -# Published to parent scope for install step. -set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) -set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) -set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) -set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) - -list(APPEND FLUTTER_LIBRARY_HEADERS - "flutter_export.h" - "flutter_windows.h" - "flutter_messenger.h" - "flutter_plugin_registrar.h" - "flutter_texture_registrar.h" -) -list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") -add_library(flutter INTERFACE) -target_include_directories(flutter INTERFACE - "${EPHEMERAL_DIR}" -) -target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") -add_dependencies(flutter flutter_assemble) - -# === Wrapper === -list(APPEND CPP_WRAPPER_SOURCES_CORE - "core_implementations.cc" - "standard_codec.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_PLUGIN - "plugin_registrar.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") -list(APPEND CPP_WRAPPER_SOURCES_APP - "flutter_engine.cc" - "flutter_view_controller.cc" -) -list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") - -# Wrapper sources needed for a plugin. -add_library(flutter_wrapper_plugin STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} -) -apply_standard_settings(flutter_wrapper_plugin) -set_target_properties(flutter_wrapper_plugin PROPERTIES - POSITION_INDEPENDENT_CODE ON) -set_target_properties(flutter_wrapper_plugin PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) -target_include_directories(flutter_wrapper_plugin PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_plugin flutter_assemble) - -# Wrapper sources needed for the runner. -add_library(flutter_wrapper_app STATIC - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_APP} -) -apply_standard_settings(flutter_wrapper_app) -target_link_libraries(flutter_wrapper_app PUBLIC flutter) -target_include_directories(flutter_wrapper_app PUBLIC - "${WRAPPER_ROOT}/include" -) -add_dependencies(flutter_wrapper_app flutter_assemble) - -# === Flutter tool backend === -# _phony_ is a non-existent file to force this command to run every time, -# since currently there's no way to get a full input/output list from the -# flutter tool. -set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") -set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) -add_custom_command( - OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} - ${PHONY_OUTPUT} - COMMAND ${CMAKE_COMMAND} -E env - ${FLUTTER_TOOL_ENVIRONMENT} - "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - ${FLUTTER_TARGET_PLATFORM} $ - VERBATIM -) -add_custom_target(flutter_assemble DEPENDS - "${FLUTTER_LIBRARY}" - ${FLUTTER_LIBRARY_HEADERS} - ${CPP_WRAPPER_SOURCES_CORE} - ${CPP_WRAPPER_SOURCES_PLUGIN} - ${CPP_WRAPPER_SOURCES_APP} -) +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index 394917c..2041a04 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -1,40 +1,40 @@ -cmake_minimum_required(VERSION 3.14) -project(runner LANGUAGES CXX) - -# Define the application target. To change its name, change BINARY_NAME in the -# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer -# work. -# -# Any new source files that you add to the application should be added here. -add_executable(${BINARY_NAME} WIN32 - "flutter_window.cpp" - "main.cpp" - "utils.cpp" - "win32_window.cpp" - "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" - "Runner.rc" - "runner.exe.manifest" -) - -# Apply the standard set of build settings. This can be removed for applications -# that need different build settings. -apply_standard_settings(${BINARY_NAME}) - -# Add preprocessor definitions for the build version. -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") -target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") - -# Disable Windows macros that collide with C++ standard library functions. -target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") - -# Add dependency libraries and include directories. Add any application-specific -# dependencies here. -target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) -target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") -target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") - -# Run the Flutter tool portions of the build. This must not be removed. -add_dependencies(${BINARY_NAME} flutter_assemble) +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 3cceed4..3c277a2 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -1,121 +1,121 @@ -// Microsoft Visual C++ generated resource script. -// -#pragma code_page(65001) -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) -#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD -#else -#define VERSION_AS_NUMBER 1,0,0,0 -#endif - -#if defined(FLUTTER_VERSION) -#define VERSION_AS_STRING FLUTTER_VERSION -#else -#define VERSION_AS_STRING "1.0.0" -#endif - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VERSION_AS_NUMBER - PRODUCTVERSION VERSION_AS_NUMBER - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS__WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904e4" - BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "myedu" "\0" - VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "myedu" "\0" - VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "myedu.exe" "\0" - VALUE "ProductName", "myedu" "\0" - VALUE "ProductVersion", VERSION_AS_STRING "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1252 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "myedu" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "myedu" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "myedu.exe" "\0" + VALUE "ProductName", "myedu" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 955ee30..c819cb0 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -1,71 +1,71 @@ -#include "flutter_window.h" - -#include - -#include "flutter/generated_plugin_registrant.h" - -FlutterWindow::FlutterWindow(const flutter::DartProject& project) - : project_(project) {} - -FlutterWindow::~FlutterWindow() {} - -bool FlutterWindow::OnCreate() { - if (!Win32Window::OnCreate()) { - return false; - } - - RECT frame = GetClientArea(); - - // The size here must match the window dimensions to avoid unnecessary surface - // creation / destruction in the startup path. - flutter_controller_ = std::make_unique( - frame.right - frame.left, frame.bottom - frame.top, project_); - // Ensure that basic setup of the controller was successful. - if (!flutter_controller_->engine() || !flutter_controller_->view()) { - return false; - } - RegisterPlugins(flutter_controller_->engine()); - SetChildContent(flutter_controller_->view()->GetNativeWindow()); - - flutter_controller_->engine()->SetNextFrameCallback([&]() { - this->Show(); - }); - - // Flutter can complete the first frame before the "show window" callback is - // registered. The following call ensures a frame is pending to ensure the - // window is shown. It is a no-op if the first frame hasn't completed yet. - flutter_controller_->ForceRedraw(); - - return true; -} - -void FlutterWindow::OnDestroy() { - if (flutter_controller_) { - flutter_controller_ = nullptr; - } - - Win32Window::OnDestroy(); -} - -LRESULT -FlutterWindow::MessageHandler(HWND hwnd, UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - // Give Flutter, including plugins, an opportunity to handle window messages. - if (flutter_controller_) { - std::optional result = - flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, - lparam); - if (result) { - return *result; - } - } - - switch (message) { - case WM_FONTCHANGE: - flutter_controller_->engine()->ReloadSystemFonts(); - break; - } - - return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h index 6da0652..28c2383 100644 --- a/windows/runner/flutter_window.h +++ b/windows/runner/flutter_window.h @@ -1,33 +1,33 @@ -#ifndef RUNNER_FLUTTER_WINDOW_H_ -#define RUNNER_FLUTTER_WINDOW_H_ - -#include -#include - -#include - -#include "win32_window.h" - -// A window that does nothing but host a Flutter view. -class FlutterWindow : public Win32Window { - public: - // Creates a new FlutterWindow hosting a Flutter view running |project|. - explicit FlutterWindow(const flutter::DartProject& project); - virtual ~FlutterWindow(); - - protected: - // Win32Window: - bool OnCreate() override; - void OnDestroy() override; - LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept override; - - private: - // The project to run. - flutter::DartProject project_; - - // The Flutter instance hosted by this window. - std::unique_ptr flutter_controller_; -}; - -#endif // RUNNER_FLUTTER_WINDOW_H_ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index f3e75ff..3c8e8f9 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -1,43 +1,43 @@ -#include -#include -#include - -#include "flutter_window.h" -#include "utils.h" - -int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { - // Attach to console when present (e.g., 'flutter run') or create a - // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { - CreateAndAttachConsole(); - } - - // Initialize COM, so that it is available for use in the library and/or - // plugins. - ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - - flutter::DartProject project(L"data"); - - std::vector command_line_arguments = - GetCommandLineArguments(); - - project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); - - FlutterWindow window(project); - Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"myedu", origin, size)) { - return EXIT_FAILURE; - } - window.SetQuitOnClose(true); - - ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - - ::CoUninitialize(); - return EXIT_SUCCESS; -} +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"myedu", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h index 66a65d1..ddc7f3e 100644 --- a/windows/runner/resource.h +++ b/windows/runner/resource.h @@ -1,16 +1,16 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by Runner.rc -// -#define IDI_APP_ICON 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest index 153653e..4b962bb 100644 --- a/windows/runner/runner.exe.manifest +++ b/windows/runner/runner.exe.manifest @@ -1,14 +1,14 @@ - - - - - PerMonitorV2 - - - - - - - - - + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp index 3a0b465..259d85b 100644 --- a/windows/runner/utils.cpp +++ b/windows/runner/utils.cpp @@ -1,65 +1,65 @@ -#include "utils.h" - -#include -#include -#include -#include - -#include - -void CreateAndAttachConsole() { - if (::AllocConsole()) { - FILE *unused; - if (freopen_s(&unused, "CONOUT$", "w", stdout)) { - _dup2(_fileno(stdout), 1); - } - if (freopen_s(&unused, "CONOUT$", "w", stderr)) { - _dup2(_fileno(stdout), 2); - } - std::ios::sync_with_stdio(); - FlutterDesktopResyncOutputStreams(); - } -} - -std::vector GetCommandLineArguments() { - // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. - int argc; - wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (argv == nullptr) { - return std::vector(); - } - - std::vector command_line_arguments; - - // Skip the first argument as it's the binary name. - for (int i = 1; i < argc; i++) { - command_line_arguments.push_back(Utf8FromUtf16(argv[i])); - } - - ::LocalFree(argv); - - return command_line_arguments; -} - -std::string Utf8FromUtf16(const wchar_t* utf16_string) { - if (utf16_string == nullptr) { - return std::string(); - } - unsigned int target_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - -1, nullptr, 0, nullptr, nullptr) - -1; // remove the trailing null character - int input_length = (int)wcslen(utf16_string); - std::string utf8_string; - if (target_length == 0 || target_length > utf8_string.max_size()) { - return utf8_string; - } - utf8_string.resize(target_length); - int converted_length = ::WideCharToMultiByte( - CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, - input_length, utf8_string.data(), target_length, nullptr, nullptr); - if (converted_length == 0) { - return std::string(); - } - return utf8_string; -} +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h index 3879d54..3f0e05c 100644 --- a/windows/runner/utils.h +++ b/windows/runner/utils.h @@ -1,19 +1,19 @@ -#ifndef RUNNER_UTILS_H_ -#define RUNNER_UTILS_H_ - -#include -#include - -// Creates a console for the process, and redirects stdout and stderr to -// it for both the runner and the Flutter library. -void CreateAndAttachConsole(); - -// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string -// encoded in UTF-8. Returns an empty std::string on failure. -std::string Utf8FromUtf16(const wchar_t* utf16_string); - -// Gets the command line arguments passed in as a std::vector, -// encoded in UTF-8. Returns an empty std::vector on failure. -std::vector GetCommandLineArguments(); - -#endif // RUNNER_UTILS_H_ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index 60608d0..b5ba2a0 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -1,288 +1,288 @@ -#include "win32_window.h" - -#include -#include - -#include "resource.h" - -namespace { - -/// Window attribute that enables dark mode window decorations. -/// -/// Redefined in case the developer's machine has a Windows SDK older than -/// version 10.0.22000.0. -/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute -#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE -#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 -#endif - -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; - -/// Registry key for app theme preference. -/// -/// A value of 0 indicates apps should use dark mode. A non-zero or missing -/// value indicates apps should use light mode. -constexpr const wchar_t kGetPreferredBrightnessRegKey[] = - L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; -constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; - -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; - -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); - -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; - } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - } - FreeLibrary(user32_module); -} - -} // namespace - -// Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: - ~WindowClassRegistrar() = default; - - // Returns the singleton registrar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { - instance_ = new WindowClassRegistrar(); - } - return instance_; - } - - // Returns the name of the window class, registering the class if it hasn't - // previously been registered. - const wchar_t* GetWindowClass(); - - // Unregisters the window class. Should only be called if there are no - // instances of the window. - void UnregisterWindowClass(); - - private: - WindowClassRegistrar() = default; - - static WindowClassRegistrar* instance_; - - bool class_registered_ = false; -}; - -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; - -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { - WNDCLASS window_class{}; - window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); - window_class.lpszClassName = kWindowClassName; - window_class.style = CS_HREDRAW | CS_VREDRAW; - window_class.cbClsExtra = 0; - window_class.cbWndExtra = 0; - window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); - window_class.hbrBackground = 0; - window_class.lpszMenuName = nullptr; - window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); - class_registered_ = true; - } - return kWindowClassName; -} - -void WindowClassRegistrar::UnregisterWindowClass() { - UnregisterClass(kWindowClassName, nullptr); - class_registered_ = false; -} - -Win32Window::Win32Window() { - ++g_active_window_count; -} - -Win32Window::~Win32Window() { - --g_active_window_count; - Destroy(); -} - -bool Win32Window::Create(const std::wstring& title, - const Point& origin, - const Size& size) { - Destroy(); - - const wchar_t* window_class = - WindowClassRegistrar::GetInstance()->GetWindowClass(); - - const POINT target_point = {static_cast(origin.x), - static_cast(origin.y)}; - HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); - UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); - double scale_factor = dpi / 96.0; - - HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), - nullptr, nullptr, GetModuleHandle(nullptr), this); - - if (!window) { - return false; - } - - UpdateTheme(window); - - return OnCreate(); -} - -bool Win32Window::Show() { - return ShowWindow(window_handle_, SW_SHOWNORMAL); -} - -// static -LRESULT CALLBACK Win32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, - reinterpret_cast(window_struct->lpCreateParams)); - - auto that = static_cast(window_struct->lpCreateParams); - EnableFullDpiSupportIfAvailable(window); - that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { - return that->MessageHandler(window, message, wparam, lparam); - } - - return DefWindowProc(window, message, wparam, lparam); -} - -LRESULT -Win32Window::MessageHandler(HWND hwnd, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; - } - - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; - - case WM_DWMCOLORIZATIONCOLORCHANGED: - UpdateTheme(hwnd); - return 0; - } - - return DefWindowProc(window_handle_, message, wparam, lparam); -} - -void Win32Window::Destroy() { - OnDestroy(); - - if (window_handle_) { - DestroyWindow(window_handle_); - window_handle_ = nullptr; - } - if (g_active_window_count == 0) { - WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); - } -} - -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( - GetWindowLongPtr(window, GWLP_USERDATA)); -} - -void Win32Window::SetChildContent(HWND content) { - child_content_ = content; - SetParent(content, window_handle_); - RECT frame = GetClientArea(); - - MoveWindow(content, frame.left, frame.top, frame.right - frame.left, - frame.bottom - frame.top, true); - - SetFocus(child_content_); -} - -RECT Win32Window::GetClientArea() { - RECT frame; - GetClientRect(window_handle_, &frame); - return frame; -} - -HWND Win32Window::GetHandle() { - return window_handle_; -} - -void Win32Window::SetQuitOnClose(bool quit_on_close) { - quit_on_close_ = quit_on_close; -} - -bool Win32Window::OnCreate() { - // No-op; provided for subclasses. - return true; -} - -void Win32Window::OnDestroy() { - // No-op; provided for subclasses. -} - -void Win32Window::UpdateTheme(HWND const window) { - DWORD light_mode; - DWORD light_mode_size = sizeof(light_mode); - LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, - kGetPreferredBrightnessRegValue, - RRF_RT_REG_DWORD, nullptr, &light_mode, - &light_mode_size); - - if (result == ERROR_SUCCESS) { - BOOL enable_dark_mode = light_mode == 0; - DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, - &enable_dark_mode, sizeof(enable_dark_mode)); - } -} +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h index e901dde..49b847f 100644 --- a/windows/runner/win32_window.h +++ b/windows/runner/win32_window.h @@ -1,102 +1,102 @@ -#ifndef RUNNER_WIN32_WINDOW_H_ -#define RUNNER_WIN32_WINDOW_H_ - -#include - -#include -#include -#include - -// A class abstraction for a high DPI-aware Win32 Window. Intended to be -// inherited from by classes that wish to specialize with custom -// rendering and input handling -class Win32Window { - public: - struct Point { - unsigned int x; - unsigned int y; - Point(unsigned int x, unsigned int y) : x(x), y(y) {} - }; - - struct Size { - unsigned int width; - unsigned int height; - Size(unsigned int width, unsigned int height) - : width(width), height(height) {} - }; - - Win32Window(); - virtual ~Win32Window(); - - // Creates a win32 window with |title| that is positioned and sized using - // |origin| and |size|. New windows are created on the default monitor. Window - // sizes are specified to the OS in physical pixels, hence to ensure a - // consistent size this function will scale the inputted width and height as - // as appropriate for the default monitor. The window is invisible until - // |Show| is called. Returns true if the window was created successfully. - bool Create(const std::wstring& title, const Point& origin, const Size& size); - - // Show the current window. Returns true if the window was successfully shown. - bool Show(); - - // Release OS resources associated with window. - void Destroy(); - - // Inserts |content| into the window tree. - void SetChildContent(HWND content); - - // Returns the backing Window handle to enable clients to set icon and other - // window properties. Returns nullptr if the window has been destroyed. - HWND GetHandle(); - - // If true, closing this window will quit the application. - void SetQuitOnClose(bool quit_on_close); - - // Return a RECT representing the bounds of the current client area. - RECT GetClientArea(); - - protected: - // Processes and route salient window messages for mouse handling, - // size change and DPI. Delegates handling of these to member overloads that - // inheriting classes can handle. - virtual LRESULT MessageHandler(HWND window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Called when CreateAndShow is called, allowing subclass window-related - // setup. Subclasses should return false if setup fails. - virtual bool OnCreate(); - - // Called when Destroy is called. - virtual void OnDestroy(); - - private: - friend class WindowClassRegistrar; - - // OS callback called by message pump. Handles the WM_NCCREATE message which - // is passed when the non-client area is being created and enables automatic - // non-client DPI scaling so that the non-client area automatically - // responds to changes in DPI. All other messages are handled by - // MessageHandler. - static LRESULT CALLBACK WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept; - - // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; - - // Update the window frame's theme to match the system theme. - static void UpdateTheme(HWND const window); - - bool quit_on_close_ = false; - - // window handle for top level window. - HWND window_handle_ = nullptr; - - // window handle for hosted content. - HWND child_content_ = nullptr; -}; - -#endif // RUNNER_WIN32_WINDOW_H_ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_