diff --git a/.github/actions/build-and-e2e-ios/action.yml b/.github/actions/build-and-e2e-ios/action.yml index 10627cb..2f74e45 100644 --- a/.github/actions/build-and-e2e-ios/action.yml +++ b/.github/actions/build-and-e2e-ios/action.yml @@ -111,7 +111,7 @@ runs: - name: Run build and prebuild step for workspace run: | npx turbo run build --filter=./typescript/apps/${{ inputs.workspace }} - yarn workspace @dbbs/${{ inputs.workspace }} prebuild + yarn workspace @dbbs/${{ inputs.workspace }} prebuild:dev shell: bash - name: Start metro server diff --git a/.github/actions/setup-flutter/action.yml b/.github/actions/setup-flutter/action.yml new file mode 100644 index 0000000..60f2aa3 --- /dev/null +++ b/.github/actions/setup-flutter/action.yml @@ -0,0 +1,24 @@ +name: 'Setup flutter' +description: 'Setup flutter with specified version from dart/.tool-versions' + +inputs: + ref: + description: Ref for checkout. Use the github.ref + required: true + +runs: + using: "composite" + steps: + - name: Extract flutter version from dart/.tool-versions + id: flutter_version + run: | + FLUTTER_VERSION="$(grep -v '#' dart/.tool-versions | grep flutter | awk '{print $2}')" + echo "FLUTTER_VERSION=$FLUTTER_VERSION" >> $GITHUB_ENV + shell: bash + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ steps.flutter_version.outputs.FLUTTER_VERSION }} + channel: 'stable' + cache: true diff --git a/.github/workflows/auto-run-e2e-mobile.yml b/.github/workflows/auto-run-e2e-mobile.yml index f669aa5..dbd4461 100644 --- a/.github/workflows/auto-run-e2e-mobile.yml +++ b/.github/workflows/auto-run-e2e-mobile.yml @@ -6,6 +6,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true +# Disabled until self-hosted mac runner issues are resolved # on: # schedule: # - cron: '0 0 * * *' @@ -13,7 +14,7 @@ concurrency: jobs: test-dev: if: ${{ github.ref || 'refs/heads/main' }} - runs-on: self-hosted + runs-on: ubuntu-24.04 timeout-minutes: 60 environment: ${{ format('{0}_{1}', 'mobile-app', 'development') }} steps: diff --git a/.github/workflows/test_dart.yml b/.github/workflows/test_dart.yml new file mode 100644 index 0000000..70980e2 --- /dev/null +++ b/.github/workflows/test_dart.yml @@ -0,0 +1,123 @@ +name: Test Dart + +run-name: Test Dart ${{ github.head_ref || github.ref }} + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + - ready_for_review + paths: + - dart/** + - .github/workflows/test_dart.yml + - .github/actions/setup-flutter.yml + - .github/actions/setup-yarn.yml + - .github/actions/setup-node.yml + push: + branches: + - main + +jobs: + test-and-build: + runs-on: [ubuntu-24.04] + environment: development + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Fetch main branch + if: github.ref != 'refs/heads/main' + run: | + git fetch origin main:main + + - name: Cache turbo setup + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + + - name: Use Setup Yarn action + uses: ./.github/actions/setup-yarn + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: '.tool-versions' + cache: 'yarn' + + - name: Setup Flutter + uses: ./.github/actions/setup-flutter + + - name: Build dependencies + run: | + yarn install --immutable + + - name: List and extract affected projects + run: | + npx turbo ls --affected --output=json > affected.json + + export AFFECTED_PATHS=$(jq -r '.packages.items + | map(select(.path | startswith("dart/")) | .path = "./" + .path) + | map(.path) + | join(" ")' affected.json) + + if [ -z "$AFFECTED_PATHS" ] && [ "$GITHUB_EVENT_NAME" != "pull_request" ]; then + AFFECTED_PATHS="./dart/**/*" + fi + + FILTER_ARGS=$(for path in $AFFECTED_PATHS; do echo "--filter=$path"; done | xargs) + + echo "FILTER_ARGS=$FILTER_ARGS" >> $GITHUB_ENV + echo "AFFECTED_PATHS=$AFFECTED_PATHS" >> $GITHUB_ENV + + echo $GITHUB_EVENT_NAME + echo $AFFECTED_PATHS + shell: bash + + - name: Install Flutter deps + if: ${{ env.AFFECTED_PATHS != '' }} + run: | + npx turbo run install:deps ${{ env.FILTER_ARGS }} + + - name: Create secret files + run: | + echo -n "${{ secrets.FLUTTER_ENV_DEVELOPMENT }}" | base64 --decode > ./dart/apps/flutter_mobile/.env.development + echo -n "${{ secrets.FLUTTER_ENV_PRODUCTION }}" | base64 --decode > ./dart/apps/flutter_mobile/.env.production + echo -n "${{ secrets.FLUTTER_FIREBASE_CREDENTIALS }}" | base64 --decode > ./dart/apps/flutter_mobile/firebase.json + echo -n "${{ secrets.FLUTTER_GOOGLE_SERVICE_ACCOUNT_ANDROID }}" | base64 --decode > ./dart/apps/flutter_mobile/android/app/google-service-account.json + echo -n "${{ secrets.FLUTTER_GOOGLE_SERVICE_ACCOUNT_IOS }}" | base64 --decode > ./dart/apps/flutter_mobile/ios/Runner/google-service-account.json + shell: bash + + - name: Typecheck + if: ${{ env.AFFECTED_PATHS != '' }} + run: | + npx turbo check-types ${{ env.FILTER_ARGS }} + + - name: Test + if: ${{ env.AFFECTED_PATHS != '' }} + run: | + npx turbo run test ${{ env.FILTER_ARGS }} + + - name: Alert on main fail + uses: slackapi/slack-github-action@v2.0.0 + if: failure() && github.ref == 'refs/heads/main' + with: + method: chat.postMessage + token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} + payload: | + channel: ${{ vars.SLACK_ALERTS_CHANNEL_ID }} + text: ":this-is-fine-fire: *GitHub Action Job auto-run-test-jobs.yml Failed*\n + *Job*: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n + *Author*: https://github.com/${{ github.actor }}" \ No newline at end of file diff --git a/.github/workflows/test_python.yml b/.github/workflows/test_python.yml index 1a36519..cd04c2d 100644 --- a/.github/workflows/test_python.yml +++ b/.github/workflows/test_python.yml @@ -27,7 +27,7 @@ on: jobs: test-and-build: - runs-on: ubuntu-24.04 + runs-on: [ubuntu-24.04] steps: - name: Checkout uses: actions/checkout@v4 @@ -88,7 +88,7 @@ jobs: - name: Install Python deps if: ${{ env.AFFECTED_PATHS != '' }} run: | - npx turbo run install:deps + npx turbo run install:deps ${{ env.FILTER_ARGS }} - name: Typecheck if: ${{ env.AFFECTED_PATHS != '' }} @@ -100,6 +100,13 @@ jobs: run: | npx turbo run test ${{ env.FILTER_ARGS }} + - name: Sonarcloud Scan + uses: sonarsource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONARCLOUD_URL: "https://sonarcloud.io" + # RUNNER_DEBUG: "1" # Enable runner debug logging for more verbose output (useful for troubleshooting, remove for normal runs) + - name: Alert on main fail uses: slackapi/slack-github-action@v2.0.0 if: failure() && github.ref == 'refs/heads/main' @@ -110,4 +117,4 @@ jobs: channel: ${{ vars.SLACK_ALERTS_CHANNEL_ID }} text: ":this-is-fine-fire: *GitHub Action Job auto-run-test-jobs.yml Failed*\n *Job*: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n - *Author*: https://github.com/${{ github.actor }}" \ No newline at end of file + *Author*: https://github.com/${{ github.actor }}" diff --git a/.github/workflows/test_typescript.yml b/.github/workflows/test_typescript.yml index 117654b..a44b518 100644 --- a/.github/workflows/test_typescript.yml +++ b/.github/workflows/test_typescript.yml @@ -35,7 +35,7 @@ on: jobs: test-and-build: - runs-on: ubuntu-24.04 + runs-on: [ubuntu-24.04] steps: - name: Checkout uses: actions/checkout@v4 @@ -89,7 +89,6 @@ jobs: echo $GITHUB_EVENT_NAME echo $AFFECTED_PATHS shell: bash - - name: Configure AWS Credentials run: | @@ -110,12 +109,19 @@ jobs: if: ${{ env.AFFECTED_PATHS != '' }} run: | npx turbo check-types ${{ env.FILTER_ARGS }} - + - name: Test if: ${{ env.AFFECTED_PATHS != '' }} run: | npx turbo run test ${{ env.FILTER_ARGS }} + - name: Sonarcloud Scan + uses: sonarsource/sonarqube-scan-action@v5 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONARCLOUD_URL: "https://sonarcloud.io" + # RUNNER_DEBUG: "1" # Enable runner debug logging for more verbose output (useful for troubleshooting, remove for normal runs) + - name: Alert on main fail uses: slackapi/slack-github-action@v2.0.0 if: failure() && github.ref == 'refs/heads/main' diff --git a/.gitignore b/.gitignore index 2bd874d..02bb370 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,9 @@ typings/ .env.development .env.stagigng .env.production +.env.* +!.env.example +*.env # parcel-bundler cache (https://parceljs.org/) .cache @@ -108,16 +111,9 @@ dist .idea .build -.DS_Store node_modules -.env -.env.* -!.env.example -*.env *.db -dist -coverage layer @@ -146,7 +142,6 @@ documentation # Xcode # -build/ *.pbxuser !default.pbxuser *.mode1v3 @@ -167,8 +162,6 @@ DerivedData # Android/IntelliJ # -build/ -.idea .gradle local.properties *.iml @@ -209,6 +202,7 @@ docker-compose/volume/* # AWS CDK .cdk.staging cdk.out +infra/aws-cdk/stacks/**/cdk.json infra/aws-cdk/**/*.js infra/aws-cdk/**/*.d.ts @@ -250,3 +244,117 @@ typescript/packages/**/lib /dotnet/apps/aspnet-server/package-lock.json /dotnet/apps/aspnet-server/DBBS.AspnetServer/weather.sqlite /dotnet/apps/aspnet-server/DBBS.AspnetServer/weather.sqlite* + +# Dart & Flutter +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +**/dgph +.fvm/ +**/firepit-log.txt +dart/**/firebase.json +dart/**/google-services.json +dart/**/GoogleService-Info.plist + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +**/*.keystore +**/*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/**/.generated/ +**/ios/**/Flutter/App.framework +**/ios/**/Flutter/Flutter.framework +**/ios/**/Flutter/Flutter.podspec +**/ios/**/Flutter/Generated.xcconfig +**/ios/**/Flutter/ephemeral/ +**/ios/**/Flutter/app.flx +**/ios/**/Flutter/app.zip +**/ios/**/Flutter/flutter_assets/ +**/ios/**/Flutter/flutter_export_environment.sh +**/ios/**/ServiceDefinitions.json +**/ios/**/Runner/GeneratedPluginRegistrant.* + +# macOS related +**/macos/Flutter/ephemeral/ +**/macos/Pods/ +**/macos/**/xcuserdata/ + +# Linux related +**/linux/flutter/ephemeral/ + +# Windows related +**/windows/flutter/ephemeral/ +**/windows/**/*.suo +**/windows/**/*.user +**/windows/**/*.userosscache +**/windows/**/*.sln.docstates +**/windows/**/x64/ +**/windows/**/x86/ +**/windows/**/*.[Cc]ache + +# 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 + +# Exceptions +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!**/windows/**/*.[Cc]ache/ +/.scannerwork/ diff --git a/.husky/pre-commit b/.husky/pre-commit index 08a186b..c594913 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,10 @@ #!/usr/bin/env sh branch_name="$(git branch --show-current)" -branch_regex="^(bug|chore|feat)\/([a-zA-Z]+-[0-9]+)(-(.*))?" + +# Original regex +#branch_regex="^(bug|chore|feat)\/([a-zA-Z]+-[0-9]+)(-(.*))?" + +branch_regex="^(bug|chore|feat)\/([a-zA-Z])?" if [[ ! $branch_name =~ $branch_regex ]]; then echo "Branch name does not match expected format. Ex. feat/TYPE-000-desc" diff --git a/Makefile b/Makefile index 3de051c..d43bfb1 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ SHELL := /bin/bash -all: setup run-build run-dev +all: setup run-build run-test # Main target for setting up dependencies setup: check-brew install-asdf make -C typescript setup make -C python setup + make -C dart setup make install-aws install-docker check-versions download-env # Target for setting up dependencies for typescript diff --git a/README.md b/README.md index 8ad171b..b74519f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # DBBS Pre-Built Solutions +[![Test Typescript](https://github.com/DBB-Software/platform/actions/workflows/test_typescript.yml/badge.svg?event=push)](https://github.com/DBB-Software/platform/actions/workflows/test_typescript.yml) +[![Test Python](https://github.com/DBB-Software/platform/actions/workflows/test_python.yml/badge.svg?event=push)](https://github.com/DBB-Software/platform/actions/workflows/test_python.yml) ## Table of Contents 1. [Introduction](#introduction) @@ -356,7 +358,8 @@ This directory encompasses a diverse array of applications, spanning web, server - Server Django App #### Mobile applications -- React Native cross-platform mobile app, can be presented using react-native-cli and expo. +- React Native cross-platform mobile app, can be presented using react-native-cli and expo +- Flutter cross-platform mobile app, integrated with turborepo for unified command execution For each application, a predefined set of initial configurations is supplied. These include: - ESLint Configuration @@ -519,6 +522,8 @@ The DBBS Pre-Built Solutions utilizes a diverse and modern technology stack to o - **[Firebase](https://firebase.google.com/): A comprehensive app development platform that offers a variety of tools and services such as authentication, real-time databases, cloud storage, and analytics, facilitating the development of high-quality apps. - **[Fastlane](https://fastlane.tools/): An open-source platform aimed at simplifying Android and iOS deployment, automating every aspect of building and releasing mobile applications. - **[Detox](https://wix.github.io/Detox/): A gray-box end-to-end testing and automation library for mobile apps, designed to test your mobile application from the perspective of a real user. +- **[Flutter](https://flutter.dev/)**: Google's UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, offering high performance and expressive design capabilities. +- **[Dart](https://dart.dev/)**: A client-optimized language for fast apps on any platform, used as the programming language for Flutter development. Our technology stack is carefully selected to ensure the platform remains at the forefront of development innovation, offering a powerful combination of speed, efficiency, and scalability. diff --git a/dart/.tool-versions b/dart/.tool-versions new file mode 100644 index 0000000..a70bbed --- /dev/null +++ b/dart/.tool-versions @@ -0,0 +1,4 @@ +java 17 +flutter 3.29.0 +ruby 3.2.0 +cocoapods 1.16.2 \ No newline at end of file diff --git a/dart/Makefile b/dart/Makefile new file mode 100644 index 0000000..4cdffe3 --- /dev/null +++ b/dart/Makefile @@ -0,0 +1,92 @@ +SHELL := /bin/bash + +# Java version from .tool-versions +JAVA_VERSION := $(shell set -e; grep "^java" .tool-versions | cut -d' ' -f2 | tr -d '\n') +FLUTTER_VERSION := $(shell set -e; grep "^flutter" .tool-versions | cut -d' ' -f2 | tr -d '\n') + +all: setup + +# Main target for setting up dependencies +setup: + make asdf-install + make check-jdk + make check-versions + sh ./../scripts/flutter-mobile-env.sh + +# Install dependencies for Flutter +install-flutter-dependencies: + @echo "Installing Flutter version $(FLUTTER_VERSION) via fvm..." + @command -v fvm >/dev/null 2>&1 || (brew tap leoafarias/fvm && brew install fvm) + fvm install $(FLUTTER_VERSION) + fvm use $(FLUTTER_VERSION) + + @echo "Flutter installed successfully." + fvm flutter doctor + +# Installs dependencies via asdf +asdf-install: + asdf plugin list | grep -q ruby || asdf plugin add ruby + asdf plugin list | grep -q cocoapods || asdf plugin add cocoapods + # asdf plugin list | grep -q dart || asdf plugin add dart + asdf plugin list | grep -q flutter || asdf plugin add flutter + + asdf plugin update --all + + asdf install ruby + asdf install cocoapods + # asdf install dart + asdf install flutter + + asdf reshim + +# Check if Java is installed and meets the required version +check-jdk: + current_version=$$(java -version 2>&1 | awk -F '"' '/version/ {print $$2}' | sed 's/^1\.//; s/\..*//'); \ + echo "Current Java version: $$current_version"; \ + echo "Required Java version: $(JAVA_VERSION)"; \ + if [ -z "$$current_version" ] || [ "$$current_version" != "$(JAVA_VERSION)" ]; then \ + echo "Java version mismatch. Installing Java $(JAVA_VERSION)..."; \ + if [[ "$$(uname)" == "Darwin" ]]; then \ + brew install openjdk@$(JAVA_VERSION); \ + sudo ln -sfn /usr/local/opt/openjdk@$(JAVA_VERSION)/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk; \ + elif [[ "$$(uname)" == "Linux" ]]; then \ + sudo apt-get update; \ + sudo apt-get install -y openjdk-$(JAVA_VERSION)-jdk; \ + fi; \ + else \ + echo "Java version $$current_version matches the required version $(JAVA_VERSION)."; \ + fi + +# Check versions +check-versions: + # Check if asdf is installed + @command -v asdf >/dev/null 2>&1 && echo "asdf is installed" || echo "asdf is not installed" + + # If asdf is installed, check all tool versions + @command -v asdf >/dev/null 2>&1 && ( \ + echo "Flutter version:"; \ + flutter --version | awk '{print $$2}' || echo "Flutter not installed"; \ + echo "Java version:"; \ + java --version | awk '{print $$2}' || echo "Java not installed"; \ + echo "Ruby version:"; \ + asdf current ruby | awk '{print $$2}' || echo "Ruby not installed"; \ + echo "Cocoapods version:"; \ + asdf current cocoapods | awk '{print $$2}' || echo "Cocoapods not installed"; \ + ) || echo "Skipping version checks as asdf is not installed" + +# Create a new Flutter app +create-app: + @read -p "Enter organization name: " org_name; \ + @read -p "Enter app name: " app_name; \ + flutter create --org com.$(org_name) --project-name $$app_name apps/$$app_name; \ + echo "Flutter app '$$app_name' created in apps/$$app_name" + +# Configure the Flutter firebase app +configure-firebase: + @if ! firebase login:list | grep -q "No users are currently logged in"; then \ + echo "Firebase: user already logged in, skipping login"; \ + else \ + echo "Firebase: requires login"; \ + firebase login; \ + fi + cd apps/$(FOLDER) && flutterfire configure diff --git a/dart/apps/flutter_mobile/.env.example b/dart/apps/flutter_mobile/.env.example new file mode 100644 index 0000000..c02087f --- /dev/null +++ b/dart/apps/flutter_mobile/.env.example @@ -0,0 +1 @@ +VARIANT=VARIANT \ No newline at end of file diff --git a/dart/apps/flutter_mobile/README.md b/dart/apps/flutter_mobile/README.md new file mode 100644 index 0000000..28277c3 --- /dev/null +++ b/dart/apps/flutter_mobile/README.md @@ -0,0 +1,103 @@ +## Name: flutter-mobile + +## Description + +The `flutter-mobile` application serves as an example of a generated Flutter mobile application within the turborepo, demonstrating a basic starting point for cross-platform mobile development projects. + +## Usage + +The application is integrated with turborepo for unified command execution through package.json: + +```bash +# Development commands +yarn ios:dev # Run on iOS simulator +yarn android:dev # Run on Android emulator + +# Production commands +yarn ios:prod # Run on iOS simulator +yarn android:prod # Run on Android emulator + +# Build commands +yarn build # Build bundle +yarn aar # Build Android AAR +yarn apk # Build Android APK +yarn appbundle # Build Android App Bundle +yarn ios # Build iOS +yarn ipa # Build iOS IPA + +# Quality assurance +yarn test # Run tests +yarn lint # Apply dart fixes and sort imports +yarn lint:show # Show potential fixes +yarn type-check # Run Flutter analyze +yarn clean # Clean build artifacts +``` + +### Setup + +1. **Environment Setup** + ```bash + make setup + ``` + This will install all required dependencies through asdf: + - Flutter 3.29.0 + - Dart 3.5.4 + - Java 17 + - Ruby 3.2.0 + - Cocoapods 1.16.2 + +### Project Structure + +``` +flutter_mobile/ +├── android/ # Android platform-specific code +├── ios/ # iOS platform-specific code +├── lib/ # Main Flutter application code +├── test/ # Test files +├── assets/ # Static assets (images, fonts, etc.) +├── pubspec.yaml # Flutter dependencies and configuration +└── package.json # Turborepo integration commands +``` + +## Feature Keywords + +- flutter-mobile-bootstrap +- cross-platform-mobile +- turborepo-integration + +## Language and framework + +- Flutter +- Dart + +## Type + +- Application + +## Tech Category + +- Mobile-app + +## Domain Category + +- Common + +## License + +The DBBS Pre-Built Solutions is open-source software licensed under the [MIT License](LICENSE). + +## Authors and owners + +- xomastyleee + +## Links + +- [Flutter Documentation](https://flutter.dev/docs) +- [Dart Documentation](https://dart.dev/guides) +- [Flutter on GitHub](https://github.com/flutter/flutter) + +## External dependencies + +- flutter +- dart +- flutter_lints \ No newline at end of file diff --git a/dart/apps/flutter_mobile/analysis_options.yaml b/dart/apps/flutter_mobile/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/dart/apps/flutter_mobile/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/dart/apps/flutter_mobile/android/app/build.gradle b/dart/apps/flutter_mobile/android/app/build.gradle new file mode 100644 index 0000000..769488c --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/build.gradle @@ -0,0 +1,46 @@ +plugins { + id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + // END: FlutterFire Configuration + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" +} + +android { + namespace = "com.dbbs.flutter_mobile" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId = "com.dbbs.flutter_mobile" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/dart/apps/flutter_mobile/android/app/src/debug/AndroidManifest.xml b/dart/apps/flutter_mobile/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/dart/apps/flutter_mobile/android/app/src/main/AndroidManifest.xml b/dart/apps/flutter_mobile/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f3e7c0d --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dart/apps/flutter_mobile/android/app/src/main/kotlin/com/dbbs/flutter_mobile/MainActivity.kt b/dart/apps/flutter_mobile/android/app/src/main/kotlin/com/dbbs/flutter_mobile/MainActivity.kt new file mode 100644 index 0000000..26bdb3c --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/main/kotlin/com/dbbs/flutter_mobile/MainActivity.kt @@ -0,0 +1,5 @@ +package com.dbbs.flutter_mobile + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/dart/apps/flutter_mobile/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/drawable/launch_background.xml b/dart/apps/flutter_mobile/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/dart/apps/flutter_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/values-night/styles.xml b/dart/apps/flutter_mobile/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/dart/apps/flutter_mobile/android/app/src/main/res/values/styles.xml b/dart/apps/flutter_mobile/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/dart/apps/flutter_mobile/android/app/src/profile/AndroidManifest.xml b/dart/apps/flutter_mobile/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/dart/apps/flutter_mobile/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/dart/apps/flutter_mobile/android/build.gradle b/dart/apps/flutter_mobile/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/dart/apps/flutter_mobile/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/dart/apps/flutter_mobile/android/gradle.properties b/dart/apps/flutter_mobile/android/gradle.properties new file mode 100644 index 0000000..2597170 --- /dev/null +++ b/dart/apps/flutter_mobile/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/dart/apps/flutter_mobile/android/gradle/wrapper/gradle-wrapper.properties b/dart/apps/flutter_mobile/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7bb2df6 --- /dev/null +++ b/dart/apps/flutter_mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/dart/apps/flutter_mobile/android/settings.gradle b/dart/apps/flutter_mobile/android/settings.gradle new file mode 100644 index 0000000..9759a22 --- /dev/null +++ b/dart/apps/flutter_mobile/android/settings.gradle @@ -0,0 +1,28 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + 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.1.0" 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 "1.8.22" apply false +} + +include ":app" diff --git a/dart/apps/flutter_mobile/assets/firebase/remote_config.json b/dart/apps/flutter_mobile/assets/firebase/remote_config.json new file mode 100644 index 0000000..9082fd2 --- /dev/null +++ b/dart/apps/flutter_mobile/assets/firebase/remote_config.json @@ -0,0 +1,44 @@ +{ + "parameters": { + "double": { + "defaultValue": { + "value": "1.0" + }, + "description": "Double type, must be DOUBLE in the remote json configuration in flutter", + "valueType": "DOUBLE" + }, + "bool": { + "defaultValue": { + "value": "true" + }, + "valueType": "BOOLEAN" + }, + "map": { + "defaultValue": { + "value": "{\"test\":\"test\"}" + }, + "valueType": "JSON" + }, + "string": { + "defaultValue": { + "value": "string" + }, + "valueType": "STRING" + }, + "int": { + "defaultValue": { + "value": "1" + }, + "valueType": "NUMBER" + } + }, + "version": { + "versionNumber": "2", + "updateTime": "2025-03-22T10:39:18.312358Z", + "updateUser": { + "email": "vlad.khomenko@dbbsoftware.com" + }, + "updateOrigin": "CONSOLE", + "updateType": "INCREMENTAL_UPDATE" + } +} \ No newline at end of file diff --git a/dart/apps/flutter_mobile/assets/images/2.0x/flutter_logo.png b/dart/apps/flutter_mobile/assets/images/2.0x/flutter_logo.png new file mode 100644 index 0000000..b65164d Binary files /dev/null and b/dart/apps/flutter_mobile/assets/images/2.0x/flutter_logo.png differ diff --git a/dart/apps/flutter_mobile/assets/images/3.0x/flutter_logo.png b/dart/apps/flutter_mobile/assets/images/3.0x/flutter_logo.png new file mode 100644 index 0000000..97e5dc9 Binary files /dev/null and b/dart/apps/flutter_mobile/assets/images/3.0x/flutter_logo.png differ diff --git a/dart/apps/flutter_mobile/assets/images/flutter_logo.png b/dart/apps/flutter_mobile/assets/images/flutter_logo.png new file mode 100644 index 0000000..b5c6ca7 Binary files /dev/null and b/dart/apps/flutter_mobile/assets/images/flutter_logo.png differ diff --git a/dart/apps/flutter_mobile/ios/Flutter/AppFrameworkInfo.plist b/dart/apps/flutter_mobile/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +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 + + diff --git a/dart/apps/flutter_mobile/ios/Podfile b/dart/apps/flutter_mobile/ios/Podfile new file mode 100644 index 0000000..2dbf7d7 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Podfile @@ -0,0 +1,43 @@ +# 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) + end +end diff --git a/dart/apps/flutter_mobile/ios/Podfile.lock b/dart/apps/flutter_mobile/ios/Podfile.lock new file mode 100644 index 0000000..63b1d75 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Podfile.lock @@ -0,0 +1,141 @@ +PODS: + - Firebase/CoreOnly (11.8.0): + - FirebaseCore (~> 11.8.0) + - Firebase/Messaging (11.8.0): + - Firebase/CoreOnly + - FirebaseMessaging (~> 11.8.0) + - Firebase/RemoteConfig (11.8.0): + - Firebase/CoreOnly + - FirebaseRemoteConfig (~> 11.8.0) + - firebase_core (3.12.1): + - Firebase/CoreOnly (= 11.8.0) + - Flutter + - firebase_messaging (15.2.4): + - Firebase/Messaging (= 11.8.0) + - firebase_core + - Flutter + - firebase_remote_config (5.4.2): + - Firebase/RemoteConfig (= 11.8.0) + - firebase_core + - Flutter + - FirebaseABTesting (11.8.0): + - FirebaseCore (~> 11.8.0) + - FirebaseCore (11.8.1): + - FirebaseCoreInternal (~> 11.8.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/Logger (~> 8.0) + - FirebaseCoreInternal (11.8.0): + - "GoogleUtilities/NSData+zlib (~> 8.0)" + - FirebaseInstallations (11.8.0): + - FirebaseCore (~> 11.8.0) + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) + - FirebaseMessaging (11.8.0): + - FirebaseCore (~> 11.8.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) + - FirebaseRemoteConfig (11.8.0): + - FirebaseABTesting (~> 11.0) + - FirebaseCore (~> 11.8.0) + - FirebaseInstallations (~> 11.0) + - FirebaseRemoteConfigInterop (~> 11.0) + - FirebaseSharedSwift (~> 11.0) + - GoogleUtilities/Environment (~> 8.0) + - "GoogleUtilities/NSData+zlib (~> 8.0)" + - FirebaseRemoteConfigInterop (11.10.0) + - FirebaseSharedSwift (11.10.0) + - Flutter (1.0.0) + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleUtilities/AppDelegateSwizzler (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.0.2): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.0.2): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.0.2): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.0.2)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.0.2) + - GoogleUtilities/Reachability (8.0.2): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.0.2): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - PromisesObjC (2.4.0) + +DEPENDENCIES: + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) + - firebase_remote_config (from `.symlinks/plugins/firebase_remote_config/ios`) + - Flutter (from `Flutter`) + +SPEC REPOS: + trunk: + - Firebase + - FirebaseABTesting + - FirebaseCore + - FirebaseCoreInternal + - FirebaseInstallations + - FirebaseMessaging + - FirebaseRemoteConfig + - FirebaseRemoteConfigInterop + - FirebaseSharedSwift + - GoogleDataTransport + - GoogleUtilities + - nanopb + - PromisesObjC + +EXTERNAL SOURCES: + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_messaging: + :path: ".symlinks/plugins/firebase_messaging/ios" + firebase_remote_config: + :path: ".symlinks/plugins/firebase_remote_config/ios" + Flutter: + :path: Flutter + +SPEC CHECKSUMS: + Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf + firebase_core: ac395f994af4e28f6a38b59e05a88ca57abeb874 + firebase_messaging: 7e223f4ee7ca053bf4ce43748e84a6d774ec9728 + firebase_remote_config: a98909ec3fd3aef7a128d5a507181c82abc8694e + FirebaseABTesting: 7d6eee42b9137541eac2610e5fea3568d956707a + FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d + FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629 + FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917 + FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8 + FirebaseRemoteConfig: f63724461fd97f0d62f20021314b59388f3e8ef8 + FirebaseRemoteConfigInterop: 7c9a9c65eff32cbb0f7bf8d18140612ad57dfcc6 + FirebaseSharedSwift: 1baacae75939499b5def867cbe34129464536a38 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + +PODFILE CHECKSUM: 251cb053df7158f337c0712f2ab29f4e0fa474ce + +COCOAPODS: 1.16.2 diff --git a/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.pbxproj b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2cea550 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,750 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 214C81FACC00D50FB3590AE7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AE900FFA087708A14874C94 /* Pods_Runner.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 */; }; + 883C1651D667CE7C1B693B05 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 40DCD9D1D6815A96FA3B0E62 /* GoogleService-Info.plist */; }; + 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 */; }; + E02162316BB78E51AC2B3B5B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 180BA1F3EEE4E180FF0BE1EB /* Pods_RunnerTests.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 */ + 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 = ""; }; + 180BA1F3EEE4E180FF0BE1EB /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 28927BCEE726EE568907CEA9 /* 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 = ""; }; + 2AE900FFA087708A14874C94 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 317A2F366C496E4D88243C62 /* 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 = ""; }; + 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 = ""; }; + 40DCD9D1D6815A96FA3B0E62 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 84896A34BF5A78F399A7AFEF /* 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 = ""; }; + 8876E55CFD83BE9C9E00886B /* 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 = ""; }; + 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 = ""; }; + A5E96F05A841A0083A64B890 /* 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 = ""; }; + D6DAAE3F56C13D485430C5B1 /* 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 */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 214C81FACC00D50FB3590AE7 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + AD4C2AC0E2F59E7D07865650 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E02162316BB78E51AC2B3B5B /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 90B978D79DFEAB10E37DD517 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 2AE900FFA087708A14874C94 /* Pods_Runner.framework */, + 180BA1F3EEE4E180FF0BE1EB /* 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 */, + 40DCD9D1D6815A96FA3B0E62 /* GoogleService-Info.plist */, + D254C5D504274DC0DC552A78 /* Pods */, + 90B978D79DFEAB10E37DD517 /* 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 = ""; + }; + D254C5D504274DC0DC552A78 /* Pods */ = { + isa = PBXGroup; + children = ( + A5E96F05A841A0083A64B890 /* Pods-Runner.debug.xcconfig */, + D6DAAE3F56C13D485430C5B1 /* Pods-Runner.release.xcconfig */, + 8876E55CFD83BE9C9E00886B /* Pods-Runner.profile.xcconfig */, + 317A2F366C496E4D88243C62 /* Pods-RunnerTests.debug.xcconfig */, + 28927BCEE726EE568907CEA9 /* Pods-RunnerTests.release.xcconfig */, + 84896A34BF5A78F399A7AFEF /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 1A09B4EDEFD4C5AD85F759CB /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + AD4C2AC0E2F59E7D07865650 /* 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 = ( + 49C6857365284958214E3836 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 497095FC5DBD2A498F2004DC /* [CP] Embed Pods Frameworks */, + 2DEBDA8291F45E547115B97F /* [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 */, + 883C1651D667CE7C1B693B05 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1A09B4EDEFD4C5AD85F759CB /* [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; + }; + 2DEBDA8291F45E547115B97F /* [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; + }; + 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"; + }; + 497095FC5DBD2A498F2004DC /* [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; + }; + 49C6857365284958214E3836 /* [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"; + }; +/* 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 = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.dbbs.flutterMobile; + 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 = 317A2F366C496E4D88243C62 /* 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.dbbs.flutterMobile.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 = 28927BCEE726EE568907CEA9 /* 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.dbbs.flutterMobile.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 = 84896A34BF5A78F399A7AFEF /* 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.dbbs.flutterMobile.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 = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.dbbs.flutterMobile; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.dbbs.flutterMobile; + 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/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/dart/apps/flutter_mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..15cada4 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dart/apps/flutter_mobile/ios/Runner.xcworkspace/contents.xcworkspacedata b/dart/apps/flutter_mobile/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/dart/apps/flutter_mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dart/apps/flutter_mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/dart/apps/flutter_mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/dart/apps/flutter_mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/dart/apps/flutter_mobile/ios/Runner/AppDelegate.swift b/dart/apps/flutter_mobile/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/AppDelegate.swift @@ -0,0 +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) + } +} diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/dart/apps/flutter_mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard b/dart/apps/flutter_mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dart/apps/flutter_mobile/ios/Runner/Base.lproj/Main.storyboard b/dart/apps/flutter_mobile/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dart/apps/flutter_mobile/ios/Runner/Info.plist b/dart/apps/flutter_mobile/ios/Runner/Info.plist new file mode 100644 index 0000000..81f4957 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Flutter Mobile + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_mobile + 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/dart/apps/flutter_mobile/ios/Runner/Runner-Bridging-Header.h b/dart/apps/flutter_mobile/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/dart/apps/flutter_mobile/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/dart/apps/flutter_mobile/ios/RunnerTests/RunnerTests.swift b/dart/apps/flutter_mobile/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/dart/apps/flutter_mobile/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/dart/apps/flutter_mobile/l10n.yaml b/dart/apps/flutter_mobile/l10n.yaml new file mode 100644 index 0000000..d480072 --- /dev/null +++ b/dart/apps/flutter_mobile/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/src/localization +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart diff --git a/dart/apps/flutter_mobile/lib/firebase_options.dart b/dart/apps/flutter_mobile/lib/firebase_options.dart new file mode 100644 index 0000000..406c681 --- /dev/null +++ b/dart/apps/flutter_mobile/lib/firebase_options.dart @@ -0,0 +1,79 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint + +// Package imports: +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; + +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: String.fromEnvironment('FIREBASE_ANDROID_API_KEY', + defaultValue: 'FIREBASE_ANDROID_API_KEY'), + appId: String.fromEnvironment('FIREBASE_ANDROID_APP_ID', + defaultValue: 'FIREBASE_ANDROID_APP_ID'), + messagingSenderId: String.fromEnvironment( + 'FIREBASE_ANDROID_MESSAGING_SENDER_ID', + defaultValue: 'FIREBASE_ANDROID_MESSAGING_SENDER_ID'), + projectId: 'dbb-software', + storageBucket: 'dbb-software.firebasestorage.app', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: String.fromEnvironment('FIREBASE_IOS_API_KEY', + defaultValue: 'FIREBASE_IOS_API_KEY'), + appId: String.fromEnvironment('FIREBASE_IOS_APP_ID', + defaultValue: 'FIREBASE_IOS_APP_ID'), + messagingSenderId: String.fromEnvironment( + 'FIREBASE_IOS_MESSAGING_SENDER_ID', + defaultValue: 'FIREBASE_IOS_MESSAGING_SENDER_ID'), + projectId: 'dbb-software', + storageBucket: 'dbb-software.firebasestorage.app', + iosBundleId: 'com.dbbs.flutterMobile', + ); +} diff --git a/dart/apps/flutter_mobile/lib/main.dart b/dart/apps/flutter_mobile/lib/main.dart new file mode 100644 index 0000000..3a68a08 --- /dev/null +++ b/dart/apps/flutter_mobile/lib/main.dart @@ -0,0 +1,28 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Project imports: +import 'src/app.dart'; +import 'src/core/utils/firebase_initializer.dart'; +import 'src/presentation/settings/settings_controller.dart'; +import 'src/presentation/settings/settings_service.dart'; + +void main() async { + // Set up the SettingsController, which will glue user settings to multiple + // Flutter Widgets. + final settingsController = SettingsController(SettingsService()); + + // Load the user's preferred theme while the splash screen is displayed. + // This prevents a sudden theme change when the app is first displayed. + await settingsController.loadSettings(); + + // Initialize Firebase + WidgetsFlutterBinding.ensureInitialized(); + await FirebaseInitializer.initialize(); + // Run the app and pass in the SettingsController. The app listens to the + // SettingsController for changes, then passes it further down to the + // SettingsView. + runApp(MyApp( + settingsController: settingsController, + remoteConfigController: FirebaseInitializer.remoteConfigController)); +} diff --git a/dart/apps/flutter_mobile/lib/src/app.dart b/dart/apps/flutter_mobile/lib/src/app.dart new file mode 100644 index 0000000..40d0520 --- /dev/null +++ b/dart/apps/flutter_mobile/lib/src/app.dart @@ -0,0 +1,93 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:firebase/main.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:provider/provider.dart'; + +// Project imports: +import 'presentation/remote_config_feature/remote_config_item_list_view.dart'; +import 'presentation/settings/settings_controller.dart'; +import 'presentation/settings/settings_view.dart'; + +/// The Widget that configures your application. +class MyApp extends StatelessWidget { + const MyApp({ + super.key, + required this.settingsController, + required this.remoteConfigController, + }); + + final SettingsController settingsController; + final RemoteConfigController remoteConfigController; + + @override + Widget build(BuildContext context) { + // Glue the SettingsController to the MaterialApp. + // + // The ListenableBuilder Widget listens to the SettingsController for changes. + // Whenever the user updates their settings, the MaterialApp is rebuilt. + return ChangeNotifierProvider.value( + value: remoteConfigController, + child: ListenableBuilder( + listenable: settingsController, + builder: (BuildContext context, Widget? child) { + return MaterialApp( + // Providing a restorationScopeId allows the Navigator built by the + // MaterialApp to restore the navigation stack when a user leaves and + // returns to the app after it has been killed while running in the + // background. + restorationScopeId: 'app', + + // Provide the generated AppLocalizations to the MaterialApp. This + // allows descendant Widgets to display the correct translations + // depending on the user's locale. + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en', ''), // English, no country code + ], + + // Use AppLocalizations to configure the correct application title + // depending on the user's locale. + // + // The appTitle is defined in .arb files found in the localization + // directory. + onGenerateTitle: (BuildContext context) => + AppLocalizations.of(context)!.appTitle, + + // Define a light and dark color theme. Then, read the user's + // preferred ThemeMode (light, dark, or system default) from the + // SettingsController to display the correct theme. + theme: ThemeData(), + darkTheme: ThemeData.dark(), + themeMode: settingsController.themeMode, + + // Define a function to handle named routes in order to support + // Flutter web url navigation and deep linking. + onGenerateRoute: (RouteSettings routeSettings) { + return MaterialPageRoute( + settings: routeSettings, + builder: (BuildContext context) { + switch (routeSettings.name) { + case SettingsView.routeName: + return SettingsView(controller: settingsController); + case SampleItemListView.routeName: + default: + return const SampleItemListView(); + } + }, + ); + }, + ); + }, + ), + ); + } +} diff --git a/dart/apps/flutter_mobile/lib/src/core/utils/firebase_initializer.dart b/dart/apps/flutter_mobile/lib/src/core/utils/firebase_initializer.dart new file mode 100644 index 0000000..ab153b9 --- /dev/null +++ b/dart/apps/flutter_mobile/lib/src/core/utils/firebase_initializer.dart @@ -0,0 +1,32 @@ +// Package imports: +import 'package:firebase/main.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; + +// Project imports: +import 'package:flutter_mobile/firebase_options.dart'; + +class FirebaseInitializer { + static final RemoteConfigController _remoteConfigController = + RemoteConfigController(FirebaseRemoteConfig.instance); + + static Future initialize() async { + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + final JsonRemoteConfigReader jsonRemoteConfigReader = + JsonRemoteConfigReader(); + + final remoteConfigContext = RemoteConfigContext( + defaultParameters: await jsonRemoteConfigReader.readDefaultValues(), + settings: RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 10), + minimumFetchInterval: const Duration(hours: 1), + ), + ); + await _remoteConfigController.initialize(remoteConfigContext); + } + + static RemoteConfigController get remoteConfigController => + _remoteConfigController; +} diff --git a/dart/apps/flutter_mobile/lib/src/localization/app_en.arb b/dart/apps/flutter_mobile/lib/src/localization/app_en.arb new file mode 100644 index 0000000..6ef053f --- /dev/null +++ b/dart/apps/flutter_mobile/lib/src/localization/app_en.arb @@ -0,0 +1,6 @@ +{ + "appTitle": "flutter_mobile", + "@appTitle": { + "description": "The title of the application" + } +} diff --git a/dart/apps/flutter_mobile/lib/src/presentation/remote_config_feature/remote_config_item_list_view.dart b/dart/apps/flutter_mobile/lib/src/presentation/remote_config_feature/remote_config_item_list_view.dart new file mode 100644 index 0000000..116ba46 --- /dev/null +++ b/dart/apps/flutter_mobile/lib/src/presentation/remote_config_feature/remote_config_item_list_view.dart @@ -0,0 +1,58 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:firebase/main.dart'; +import 'package:provider/provider.dart'; + +// Project imports: +import '../settings/settings_view.dart'; + +/// Displays a list of SampleItems. +class SampleItemListView extends StatelessWidget { + const SampleItemListView({ + super.key, + }); + + static const routeName = '/'; + + @override + Widget build(BuildContext context) { + final remoteConfig = context.watch(); + final configValues = remoteConfig.getAllValueAsList(); + + return Scaffold( + appBar: AppBar( + title: const Text('Remote Config Values'), + actions: [ + IconButton( + icon: const Icon(Icons.settings), + onPressed: () { + Navigator.restorablePushNamed(context, SettingsView.routeName); + }, + ), + ], + ), + body: ListView.builder( + restorationId: 'remoteConfigListView', + itemCount: configValues.length, + itemBuilder: (BuildContext context, int index) { + final entry = configValues[index]; + final key = entry.key; + final parsedValue = entry.value; + + return ListTile( + title: Text(key), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Value: ${parsedValue?.value}'), + Text('Source: ${parsedValue?.source}'), + ], + ), + ); + }, + ), + ); + } +} diff --git a/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_controller.dart b/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_controller.dart new file mode 100644 index 0000000..5b1955b --- /dev/null +++ b/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_controller.dart @@ -0,0 +1,52 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Project imports: +import 'settings_service.dart'; + +/// A class that many Widgets can interact with to read user settings, update +/// user settings, or listen to user settings changes. +/// +/// Controllers glue Data Services to Flutter Widgets. The SettingsController +/// uses the SettingsService to store and retrieve user settings. +class SettingsController with ChangeNotifier { + SettingsController(this._settingsService); + + // Make SettingsService a private variable so it is not used directly. + final SettingsService _settingsService; + + // Make ThemeMode a private variable so it is not updated directly without + // also persisting the changes with the SettingsService. + late ThemeMode _themeMode; + + // Allow Widgets to read the user's preferred ThemeMode. + ThemeMode get themeMode => _themeMode; + + /// Load the user's settings from the SettingsService. It may load from a + /// local database or the internet. The controller only knows it can load the + /// settings from the service. + Future loadSettings() async { + _themeMode = await _settingsService.themeMode(); + + // Important! Inform listeners a change has occurred. + notifyListeners(); + } + + /// Update and persist the ThemeMode based on the user's selection. + Future updateThemeMode(ThemeMode? newThemeMode) async { + if (newThemeMode == null) return; + + // Do not perform any work if new and old ThemeMode are identical + if (newThemeMode == _themeMode) return; + + // Otherwise, store the new ThemeMode in memory + _themeMode = newThemeMode; + + // Important! Inform listeners a change has occurred. + notifyListeners(); + + // Persist the changes to a local database or the internet using the + // SettingService. + await _settingsService.updateThemeMode(newThemeMode); + } +} diff --git a/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_service.dart b/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_service.dart new file mode 100644 index 0000000..4066fa8 --- /dev/null +++ b/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_service.dart @@ -0,0 +1,18 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +/// A service that stores and retrieves user settings. +/// +/// By default, this class does not persist user settings. If you'd like to +/// persist the user settings locally, use the shared_preferences package. If +/// you'd like to store settings on a web server, use the http package. +class SettingsService { + /// Loads the User's preferred ThemeMode from local or remote storage. + Future themeMode() async => ThemeMode.system; + + /// Persists the user's preferred ThemeMode to local or remote storage. + Future updateThemeMode(ThemeMode theme) async { + // Use the shared_preferences package to persist settings locally or the + // http package to persist settings over the network. + } +} diff --git a/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_view.dart b/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_view.dart new file mode 100644 index 0000000..84d98aa --- /dev/null +++ b/dart/apps/flutter_mobile/lib/src/presentation/settings/settings_view.dart @@ -0,0 +1,55 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Project imports: +import 'settings_controller.dart'; + +/// Displays the various settings that can be customized by the user. +/// +/// When a user changes a setting, the SettingsController is updated and +/// Widgets that listen to the SettingsController are rebuilt. +class SettingsView extends StatelessWidget { + const SettingsView({super.key, required this.controller}); + + static const routeName = '/settings'; + static const String variant = + String.fromEnvironment('VARIANT', defaultValue: 'default'); + + final SettingsController controller; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Settings, for App variant: $variant'), + ), + body: Padding( + padding: const EdgeInsets.all(16), + // Glue the SettingsController to the theme selection DropdownButton. + // + // When a user selects a theme from the dropdown list, the + // SettingsController is updated, which rebuilds the MaterialApp. + child: DropdownButton( + // Read the selected themeMode from the controller + value: controller.themeMode, + // Call the updateThemeMode method any time the user selects a theme. + onChanged: controller.updateThemeMode, + items: const [ + DropdownMenuItem( + value: ThemeMode.system, + child: Text('System Theme'), + ), + DropdownMenuItem( + value: ThemeMode.light, + child: Text('Light Theme'), + ), + DropdownMenuItem( + value: ThemeMode.dark, + child: Text('Dark Theme'), + ) + ], + ), + ), + ); + } +} diff --git a/dart/apps/flutter_mobile/package.json b/dart/apps/flutter_mobile/package.json new file mode 100644 index 0000000..57dd2cf --- /dev/null +++ b/dart/apps/flutter_mobile/package.json @@ -0,0 +1,27 @@ +{ + "name": "@dbbs/flutter-mobile", + "version": "0.0.1", + "private": true, + "scripts": { + "install:deps": "flutter pub get", + "build": "flutter pub run build_runner build && yarn bundle", + "test": "flutter test", + "lint": "dart fix --apply && flutter pub run import_sorter:main", + "lint:show": "dart fix --dry-run", + "check-types": "flutter analyze", + "clean": "flutter clean", + "aar": "flutter build aar", + "apk": "flutter build apk", + "appbundle": "flutter build appbundle", + "ios": "flutter build ios", + "ipa": "flutter build ipa", + "bundle": "flutter build bundle", + "ios:dev": "flutter run -d 'iPhone 16 Pro' --dart-define-from-file=.env.development", + "ios:dev:prod": "flutter run -d 'iPhone 16 Pro' --dart-define-from-file=.env.production", + "android:dev": "flutter run -d 'emulator-5554' --dart-define-from-file=.env.development", + "android:dev:prod": "flutter run -d 'emulator-5554' --dart-define-from-file=.env.production" + }, + "devDependencies": { + "@dbbs/flutter-firebase": "*" + } +} diff --git a/dart/apps/flutter_mobile/pubspec.lock b/dart/apps/flutter_mobile/pubspec.lock new file mode 100644 index 0000000..ebfae71 --- /dev/null +++ b/dart/apps/flutter_mobile/pubspec.lock @@ -0,0 +1,718 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + url: "https://pub.dev" + source: hosted + version: "80.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5" + url: "https://pub.dev" + source: hosted + version: "1.3.53" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + url: "https://pub.dev" + source: hosted + version: "2.4.15" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + url: "https://pub.dev" + source: hosted + version: "8.9.5" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + 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" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + firebase: + dependency: "direct main" + description: + path: "../../packages/firebase" + relative: true + source: path + version: "0.0.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b + url: "https://pub.dev" + source: hosted + version: "3.12.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: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493 + url: "https://pub.dev" + source: hosted + version: "2.21.1" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "5fc345c6341f9dc69fd0ffcbf508c784fd6d1b9e9f249587f30434dd8b6aa281" + url: "https://pub.dev" + source: hosted + version: "15.2.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: a935924cf40925985c8049df4968b1dde5c704f570f3ce380b31d3de6990dd94 + url: "https://pub.dev" + source: hosted + version: "4.6.4" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: fafebf6a1921931334f3f10edb5037a5712288efdd022881e2d093e5654a2fd4 + url: "https://pub.dev" + source: hosted + version: "3.10.4" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: "908fe47d1aea2dbb6c6f3ef93ae884b34bafddf696495949f75c981a47f6704e" + url: "https://pub.dev" + source: hosted + version: "5.4.2" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: "0914e2680235cca3e0c88ad98ff387d38c5455ec8d24ad70649a15e6a872295b" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: "7fd610cf20077fb1b3383573968e3477864272f540788b6da24e4fab395f68bb" + url: "https://pub.dev" + source: hosted + version: "1.8.2" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + 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" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + import_sorter: + dependency: "direct dev" + description: + name: import_sorter + sha256: eb15738ccead84e62c31e0208ea4e3104415efcd4972b86906ca64a1187d0836 + url: "https://pub.dev" + source: hosted + version: "4.6.0" + intl: + dependency: transitive + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.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: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + 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" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 + url: "https://pub.dev" + source: hosted + version: "5.4.5" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + 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" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + tint: + dependency: transitive + description: + name: tint + sha256: "9652d9a589f4536d5e392cf790263d120474f15da3cf1bee7f1fdb31b4de5f46" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + 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" + watcher: + dependency: transitive + description: + name: watcher + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.29.0" diff --git a/dart/apps/flutter_mobile/pubspec.yaml b/dart/apps/flutter_mobile/pubspec.yaml new file mode 100644 index 0000000..b82085b --- /dev/null +++ b/dart/apps/flutter_mobile/pubspec.yaml @@ -0,0 +1,52 @@ +name: flutter_mobile +description: A new Flutter application + +# Prevent accidental publishing to pub.dev. +publish_to: 'none' + +version: 0.0.1+1 + +environment: + sdk: ">=3.5.4 <4.0.0" + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + 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.5 + + # Firebase packages with custom package path + firebase: + path: ../../packages/firebase + firebase_core: ^3.12.1 + firebase_messaging: ^15.2.4 + firebase_remote_config: ^5.4.2 + + # State management + provider: ^6.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.1 + import_sorter: ^4.6.0 + mockito: ^5.4.2 + build_runner: ^2.4.6 + +flutter: + uses-material-design: true + + # Enable generation of localized Strings from arb files. + generate: true + + assets: + # Add assets from the images directory to the application. + - .env.development + - .env.production + - assets/images/ + - assets/firebase/ diff --git a/dart/apps/flutter_mobile/test/unit_test.dart b/dart/apps/flutter_mobile/test/unit_test.dart new file mode 100644 index 0000000..16cbadc --- /dev/null +++ b/dart/apps/flutter_mobile/test/unit_test.dart @@ -0,0 +1,16 @@ +// This is an example unit test. +// +// A unit test tests a single function, method, or class. To learn more about +// writing unit tests, visit +// https://flutter.dev/to/unit-testing + +// Package imports: +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Plus Operator', () { + test('should add two numbers together', () { + expect(1 + 1, 2); + }); + }); +} diff --git a/dart/apps/flutter_mobile/test/widget_test.dart b/dart/apps/flutter_mobile/test/widget_test.dart new file mode 100644 index 0000000..2309b39 --- /dev/null +++ b/dart/apps/flutter_mobile/test/widget_test.dart @@ -0,0 +1,34 @@ +// This is an example 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. +// +// Visit https://flutter.dev/to/widget-testing for +// more information about Widget testing. + +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('MyWidget', () { + testWidgets('should display a string of text', (WidgetTester tester) async { + // Define a Widget + const myWidget = MaterialApp( + home: Scaffold( + body: Text('Hello'), + ), + ); + + // Build myWidget and trigger a frame. + await tester.pumpWidget(myWidget); + + // Verify myWidget shows some text + expect(find.byType(Text), findsOneWidget); + }); + }); +} diff --git a/dart/packages/firebase/.metadata b/dart/packages/firebase/.metadata new file mode 100644 index 0000000..ce9baf6 --- /dev/null +++ b/dart/packages/firebase/.metadata @@ -0,0 +1,27 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" + channel: "stable" + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/dart/packages/firebase/README.md b/dart/packages/firebase/README.md new file mode 100644 index 0000000..b56fdc2 --- /dev/null +++ b/dart/packages/firebase/README.md @@ -0,0 +1,148 @@ +## Name: firebase + +## Description + +The `firebase` package provides Firebase functionalities for Flutter applications, enabling seamless integration with Firebase services. This package includes support for Firebase Remote Config and Cloud Messaging. + +## Usage + +Install the package using pubspec.yaml: + +```yaml +dependencies: + firebase: + path: ../packages/firebase +``` + +### Remote Config + +The `RemoteConfigController` provides a way to manage remote configuration values from Firebase. It can be used to fetch, activate, and access these values throughout your application. + +#### Example Usage + +```dart +import 'package:firebase/firebase.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize Firebase + await Firebase.initializeApp(); + + // Create controller + final remoteConfigController = RemoteConfigController(FirebaseRemoteConfig.instance); + + // Setup remote config defaults and settings + final parameters = { + 'welcome_message': RemoteConfigParameter( + defaultValue: 'Hello, world!', + valueType: 'STRING', + ), + 'feature_enabled': RemoteConfigParameter( + defaultValue: 'false', + valueType: 'BOOLEAN', + ), + }; + + final settings = RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 10), + minimumFetchInterval: const Duration(hours: 1), + ); + + final context = RemoteConfigContext( + defaultParameters: parameters, + settings: settings, + ); + + // Initialize remote config + await remoteConfigController.initialize(context); + + runApp( + ChangeNotifierProvider.value( + value: remoteConfigController, + child: MyApp(), + ), + ); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + // Access remote config values + final config = context.watch(); + final welcomeMessage = config.getSingleValue('welcome_message'); + final featureEnabled = config.getSingleValue('feature_enabled'); + + return MaterialApp( + home: Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(welcomeMessage?.value ?? 'Loading...'), + if (featureEnabled?.value == true) + Text('Feature enabled!'), + ], + ), + ), + ), + ); + } +} +``` + +### Available Methods + +- `initialize(RemoteConfigContext)`: Sets up the remote configuration with defaults and fetches values from the server +- `getSingleValue(String)`: Gets a single value by key with proper typing +- `getAllValueAsMap()`: Gets all values as a Map +- `getAllValueAsList()`: Gets all values as a List of MapEntry + +## Feature Keywords + +- firebase +- firebase-remote-config +- firebase-cloud-messaging +- flutter-firebase + +## Language and framework + +- Flutter +- Dart + +## Type + +- Package + +## Tech Category + +- Mobile-app +- Frontend + +## Domain Category + +- Common + +## License + +The DBBS Pre-Built Solutions is open-source software licensed under the [MIT License](LICENSE). + +## Authors and owners + +- xomastyleee + +## Links + +- [Firebase Flutter Documentation](https://firebase.flutter.dev/) +- [Flutter Remote Config Documentation](https://firebase.flutter.dev/docs/remote-config/overview/) + +## External dependencies + +- flutter +- firebase_core +- firebase_remote_config +- firebase_messaging +- provider \ No newline at end of file diff --git a/dart/packages/firebase/analysis_options.yaml b/dart/packages/firebase/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/dart/packages/firebase/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/dart/packages/firebase/lib/main.dart b/dart/packages/firebase/lib/main.dart new file mode 100644 index 0000000..ca6d8d9 --- /dev/null +++ b/dart/packages/firebase/lib/main.dart @@ -0,0 +1,7 @@ +library firebase; + +// Remote Config exports: +export 'src/remote-config/remote_config_controller.dart'; +export 'src/remote-config/remote_config_context.dart'; +export 'src/remote-config/remote_config_value_parser.dart'; +export 'src/remote-config/json_remote_config_reader.dart'; diff --git a/dart/packages/firebase/lib/src/remote-config/json_remote_config_reader.dart b/dart/packages/firebase/lib/src/remote-config/json_remote_config_reader.dart new file mode 100644 index 0000000..a325b81 --- /dev/null +++ b/dart/packages/firebase/lib/src/remote-config/json_remote_config_reader.dart @@ -0,0 +1,28 @@ +// Dart imports: +import 'dart:convert'; + +// Flutter imports: +import 'package:flutter/services.dart'; + +// Project imports: +import 'remote_config_context.dart'; + +class JsonRemoteConfigReader { + String filePath = "assets/firebase/remote_config.json"; + + JsonRemoteConfigReader(); + + set updateFilePath(String newFilePath) { + filePath = newFilePath; + } + + Future readDefaultValues() async { + try { + final response = await rootBundle.loadString(filePath); + final Map jsonData = json.decode(response); + return RemoteConfigDefaultValuesExtension.fromJson(jsonData); + } catch (e) { + throw Exception('Failed to read remote config: $e'); + } + } +} diff --git a/dart/packages/firebase/lib/src/remote-config/remote_config_context.dart b/dart/packages/firebase/lib/src/remote-config/remote_config_context.dart new file mode 100644 index 0000000..c8e6093 --- /dev/null +++ b/dart/packages/firebase/lib/src/remote-config/remote_config_context.dart @@ -0,0 +1,43 @@ +// Package imports: +import 'package:firebase_remote_config/firebase_remote_config.dart'; + +typedef RemoteConfigDefaultValues = Map; + +class RemoteConfigParameter { + final dynamic defaultValue; + final String valueType; + + RemoteConfigParameter({ + required this.defaultValue, + required this.valueType, + }); + + factory RemoteConfigParameter.fromJson(Map json) { + return RemoteConfigParameter( + defaultValue: json['defaultValue']['value'], + valueType: json['valueType'], + ); + } +} + +extension RemoteConfigDefaultValuesExtension on RemoteConfigDefaultValues { + static RemoteConfigDefaultValues fromJson(Map json) { + final parametersJson = json['parameters'] as Map; + return parametersJson.map( + (key, value) => MapEntry( + key, + RemoteConfigParameter.fromJson(value as Map), + ), + ); + } +} + +class RemoteConfigContext { + final RemoteConfigDefaultValues defaultParameters; + final RemoteConfigSettings settings; + + const RemoteConfigContext({ + required this.defaultParameters, + required this.settings, + }); +} diff --git a/dart/packages/firebase/lib/src/remote-config/remote_config_controller.dart b/dart/packages/firebase/lib/src/remote-config/remote_config_controller.dart new file mode 100644 index 0000000..6203a83 --- /dev/null +++ b/dart/packages/firebase/lib/src/remote-config/remote_config_controller.dart @@ -0,0 +1,67 @@ +// Flutter imports: +import 'package:flutter/foundation.dart'; + +// Package imports: +import 'package:firebase_remote_config/firebase_remote_config.dart'; + +// Project imports: +import 'remote_config_context.dart'; +import 'remote_config_value_parser.dart'; + +class RemoteConfigController extends ChangeNotifier { + late RemoteConfigDefaultValues _defaultParameters; + + final FirebaseRemoteConfig _remoteConfig; + + final RemoteConfigValueParser _parser = RemoteConfigValueParser(); + + RemoteConfigController(this._remoteConfig); + + Future initialize(RemoteConfigContext context) async { + try { + await _remoteConfig.setConfigSettings(context.settings); + + final defaults = context.defaultParameters.map( + (key, parameter) => MapEntry(key, parameter.defaultValue), + ); + await _remoteConfig.setDefaults(defaults); + + _defaultParameters = context.defaultParameters; + + await _remoteConfig.fetchAndActivate(); + notifyListeners(); + } catch (e) { + throw Exception(e); + } + } + + RemoteConfigDefaultValues get defaultParameters => _defaultParameters; + + ParsedValue? getSingleValue(String key) { + try { + final value = _remoteConfig.getValue(key); + return _parser.parseConfigValue( + value, _defaultParameters[key]?.valueType); + } catch (e) { + return null; + } + } + + Map?> getAllValueAsMap() { + final allValues = _remoteConfig.getAll(); + return Map.fromEntries( + allValues.entries.map((entry) { + final key = entry.key; + final value = entry.value; + final valueType = _defaultParameters[key]?.valueType; + + return MapEntry( + key, _parser.parseConfigValue(value, valueType)); + }), + ); + } + + List?>> getAllValueAsList() { + return getAllValueAsMap().entries.toList(); + } +} diff --git a/dart/packages/firebase/lib/src/remote-config/remote_config_value_parser.dart b/dart/packages/firebase/lib/src/remote-config/remote_config_value_parser.dart new file mode 100644 index 0000000..93fdcda --- /dev/null +++ b/dart/packages/firebase/lib/src/remote-config/remote_config_value_parser.dart @@ -0,0 +1,51 @@ +// Dart imports: +import 'dart:convert'; + +// Package imports: +import 'package:firebase_remote_config/firebase_remote_config.dart'; + +class ParsedValue { + final T value; + final String source; + + ParsedValue({ + required this.value, + required this.source, + }); +} + +class RemoteConfigValueParser { + ParsedValue? parseConfigValue( + RemoteConfigValue value, String? valueType) { + dynamic parsedValue; + + switch (valueType) { + case "STRING": + parsedValue = value.asString(); + break; + case "NUMBER": + parsedValue = value.asInt(); + break; + case "DOUBLE": + parsedValue = value.asDouble(); + break; + case "BOOLEAN": + parsedValue = value.asBool(); + break; + case "JSON": + try { + parsedValue = jsonDecode(value.asString()); + } catch (e) { + return null; + } + break; + default: + return null; + } + + return ParsedValue( + value: parsedValue as T, + source: value.source.name, + ); + } +} diff --git a/dart/packages/firebase/package.json b/dart/packages/firebase/package.json new file mode 100644 index 0000000..e405bee --- /dev/null +++ b/dart/packages/firebase/package.json @@ -0,0 +1,14 @@ +{ + "name": "@dbbs/flutter-firebase", + "version": "0.0.1", + "private": true, + "scripts": { + "install:deps": "flutter pub get", + "build": "flutter pub run build_runner build", + "test": "flutter test", + "lint": "dart fix --apply && flutter pub run import_sorter:main", + "lint:show": "dart fix --dry-run", + "check-types": "flutter analyze", + "clean": "flutter clean" + } +} diff --git a/dart/packages/firebase/pubspec.lock b/dart/packages/firebase/pubspec.lock new file mode 100644 index 0000000..449c387 --- /dev/null +++ b/dart/packages/firebase/pubspec.lock @@ -0,0 +1,674 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + url: "https://pub.dev" + source: hosted + version: "80.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5" + url: "https://pub.dev" + source: hosted + version: "1.3.53" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + url: "https://pub.dev" + source: hosted + version: "2.4.15" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + url: "https://pub.dev" + source: hosted + version: "8.0.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + url: "https://pub.dev" + source: hosted + version: "8.9.5" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b + url: "https://pub.dev" + source: hosted + version: "3.12.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: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493 + url: "https://pub.dev" + source: hosted + version: "2.21.1" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "5fc345c6341f9dc69fd0ffcbf508c784fd6d1b9e9f249587f30434dd8b6aa281" + url: "https://pub.dev" + source: hosted + version: "15.2.4" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: a935924cf40925985c8049df4968b1dde5c704f570f3ce380b31d3de6990dd94 + url: "https://pub.dev" + source: hosted + version: "4.6.4" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: fafebf6a1921931334f3f10edb5037a5712288efdd022881e2d093e5654a2fd4 + url: "https://pub.dev" + source: hosted + version: "3.10.4" + firebase_remote_config: + dependency: "direct main" + description: + name: firebase_remote_config + sha256: "908fe47d1aea2dbb6c6f3ef93ae884b34bafddf696495949f75c981a47f6704e" + url: "https://pub.dev" + source: hosted + version: "5.4.2" + firebase_remote_config_platform_interface: + dependency: transitive + description: + name: firebase_remote_config_platform_interface + sha256: "0914e2680235cca3e0c88ad98ff387d38c5455ec8d24ad70649a15e6a872295b" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + firebase_remote_config_web: + dependency: transitive + description: + name: firebase_remote_config_web + sha256: "7fd610cf20077fb1b3383573968e3477864272f540788b6da24e4fab395f68bb" + url: "https://pub.dev" + source: hosted + version: "1.8.2" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + import_sorter: + dependency: "direct dev" + description: + name: import_sorter + sha256: eb15738ccead84e62c31e0208ea4e3104415efcd4972b86906ca64a1187d0836 + url: "https://pub.dev" + source: hosted + version: "4.6.0" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.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: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + 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" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 + url: "https://pub.dev" + source: hosted + version: "5.4.5" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + 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" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" + source: hosted + version: "0.7.4" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + tint: + dependency: transitive + description: + name: tint + sha256: "9652d9a589f4536d5e392cf790263d120474f15da3cf1bee7f1fdb31b4de5f46" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + 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" + watcher: + dependency: transitive + description: + name: watcher + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.29.0" diff --git a/dart/packages/firebase/pubspec.yaml b/dart/packages/firebase/pubspec.yaml new file mode 100644 index 0000000..851470d --- /dev/null +++ b/dart/packages/firebase/pubspec.yaml @@ -0,0 +1,80 @@ +name: firebase +description: Firebase package for the platform +version: 0.0.1 +publish_to: none + +environment: + sdk: '>=3.5.4 <4.0.0' + flutter: ">=3.29.0" + +dependencies: + flutter: + sdk: flutter + firebase_core: ^3.12.1 + firebase_messaging: ^15.2.4 + firebase_remote_config: ^5.4.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.1 + import_sorter: ^4.6.0 + mockito: ^5.4.2 + build_runner: ^2.4.6 + +# 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: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + # This plugin project was generated without specifying any + # platforms with the `--platform` argument. If you see the `some_platform` map below, remove it and + # then add platforms following the instruction here: + # https://flutter.dev/to/pubspec-plugin-platforms + # ------------------- + some_platform: + pluginClass: somePluginClass + # ------------------- + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your plugin package, 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 in packages, see + # https://flutter.dev/to/font-from-package diff --git a/dart/packages/firebase/test/remote-config/remote_config_context_test.dart b/dart/packages/firebase/test/remote-config/remote_config_context_test.dart new file mode 100644 index 0000000..2cb656a --- /dev/null +++ b/dart/packages/firebase/test/remote-config/remote_config_context_test.dart @@ -0,0 +1,92 @@ +// Package imports: +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// Project imports: +import 'package:firebase/src/remote-config/remote_config_context.dart'; + +void main() { + group('RemoteConfigParameter', () { + test('creates instance with required parameters', () { + // Act + final parameter = RemoteConfigParameter( + defaultValue: 'test', + valueType: 'STRING', + ); + + // Assert + expect(parameter.defaultValue, equals('test')); + expect(parameter.valueType, equals('STRING')); + }); + + test('creates instance from JSON', () { + // Arrange + final json = { + 'defaultValue': {'value': 'test-value'}, + 'valueType': 'STRING', + }; + + // Act + final parameter = RemoteConfigParameter.fromJson(json); + + // Assert + expect(parameter.defaultValue, equals('test-value')); + expect(parameter.valueType, equals('STRING')); + }); + }); + + group('RemoteConfigDefaultValuesExtension', () { + test('creates map from JSON', () { + // Arrange + final json = { + 'parameters': { + 'string': { + 'defaultValue': {'value': 'test-string'}, + 'valueType': 'STRING', + }, + 'number': { + 'defaultValue': {'value': '42'}, + 'valueType': 'NUMBER', + }, + }, + }; + + // Act + final values = RemoteConfigDefaultValuesExtension.fromJson(json); + + // Assert + expect(values, isA>()); + expect(values.length, equals(2)); + expect(values['string']?.defaultValue, equals('test-string')); + expect(values['string']?.valueType, equals('STRING')); + expect(values['number']?.defaultValue, equals('42')); + expect(values['number']?.valueType, equals('NUMBER')); + }); + }); + + group('RemoteConfigContext', () { + test('creates instance with required parameters', () { + // Arrange + final parameters = { + 'test': RemoteConfigParameter( + defaultValue: 'value', + valueType: 'STRING', + ), + }; + final settings = RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 10), + minimumFetchInterval: const Duration(hours: 1), + ); + + // Act + final context = RemoteConfigContext( + defaultParameters: parameters, + settings: settings, + ); + + // Assert + expect(context.defaultParameters, equals(parameters)); + expect(context.settings, equals(settings)); + }); + }); +} diff --git a/dart/packages/firebase/test/remote-config/remote_config_controller_test.dart b/dart/packages/firebase/test/remote-config/remote_config_controller_test.dart new file mode 100644 index 0000000..1314616 --- /dev/null +++ b/dart/packages/firebase/test/remote-config/remote_config_controller_test.dart @@ -0,0 +1,153 @@ +// Package imports: +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +// Project imports: +import 'package:firebase/src/remote-config/remote_config_context.dart'; +import 'package:firebase/src/remote-config/remote_config_controller.dart'; +import 'package:firebase/src/remote-config/remote_config_value_parser.dart'; + +class MockFirebaseRemoteConfig extends Mock implements FirebaseRemoteConfig { + @override + Future setDefaults(Map defaults) async {} + + @override + Future setConfigSettings(RemoteConfigSettings settings) async {} + + @override + Future fetchAndActivate() async => true; + + @override + RemoteConfigValue getValue(String key) { + return MockRemoteConfigValue(); + } + + @override + Map getAll() { + return { + 'string': MockRemoteConfigValue(), + 'number': MockRemoteConfigValue(), + }; + } +} + +class MockRemoteConfigValue extends Mock implements RemoteConfigValue { + final String _stringValue = 'remote-string-value'; + final int _intValue = 100; + final double _doubleValue = 3.14; + final bool _boolValue = true; + + @override + ValueSource get source => ValueSource.valueStatic; + + @override + String asString() => _stringValue; + + @override + int asInt() => _intValue; + + @override + double asDouble() => _doubleValue; + + @override + bool asBool() => _boolValue; +} + +class MockRemoteConfigSettings extends Mock implements RemoteConfigSettings {} + +void main() { + group('RemoteConfigController', () { + late RemoteConfigController controller; + late MockFirebaseRemoteConfig mockRemoteConfig; + late RemoteConfigContext mockContext; + late MockRemoteConfigSettings mockSettings; + + setUp(() { + mockRemoteConfig = MockFirebaseRemoteConfig(); + mockSettings = MockRemoteConfigSettings(); + + final parameters = { + 'string': RemoteConfigParameter( + defaultValue: 'default-string', + valueType: 'STRING', + ), + 'number': RemoteConfigParameter( + defaultValue: '42', + valueType: 'NUMBER', + ), + }; + + mockContext = RemoteConfigContext( + defaultParameters: parameters, + settings: mockSettings, + ); + + controller = RemoteConfigController(mockRemoteConfig); + }); + + test('initialize sets up remote config correctly', () async { + // Act + await controller.initialize(mockContext); + + // Assert + expect( + controller.defaultParameters, equals(mockContext.defaultParameters)); + }); + + test('getSingleValue returns parsed value', () async { + // Arrange - custom mock setup + await controller.initialize(mockContext); + + // Act + final result = controller.getSingleValue('string'); + + // Assert + expect(result, isNotNull); + expect(result!.value, equals('remote-string-value')); + expect(result.source, equals('valueStatic')); + }); + + test('getAllValueAsMap returns map of parsed values', () async { + // Arrange + await controller.initialize(mockContext); + + // Act + final result = controller.getAllValueAsMap(); + + // Assert + expect(result, isA?>>()); + expect(result.length, equals(2)); + }); + + test('getAllValueAsList returns list of parsed values', () async { + // Arrange + await controller.initialize(mockContext); + + // Act + final result = controller.getAllValueAsList(); + + // Assert + expect(result, isA?>>>()); + expect(result.length, equals(2)); + }); + + test('initialize throws exception when config fails', () async { + // Create a new mock that throws an exception + final failingMock = FailingMockFirebaseRemoteConfig(); + controller = RemoteConfigController(failingMock); + + // Act & Assert + expect(() => controller.initialize(mockContext), throwsException); + }); + }); +} + +// Special mock for testing exceptions +class FailingMockFirebaseRemoteConfig extends Mock + implements FirebaseRemoteConfig { + @override + Future setConfigSettings(RemoteConfigSettings settings) async { + throw Exception('Config failed'); + } +} diff --git a/dart/packages/firebase/test/remote-config/remote_config_value_parser_test.dart b/dart/packages/firebase/test/remote-config/remote_config_value_parser_test.dart new file mode 100644 index 0000000..2cdd41e --- /dev/null +++ b/dart/packages/firebase/test/remote-config/remote_config_value_parser_test.dart @@ -0,0 +1,154 @@ +// Package imports: +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +// Project imports: +import 'package:firebase/src/remote-config/remote_config_value_parser.dart'; + +// Dynamic test values mock +class MockRemoteConfigValue extends Mock implements RemoteConfigValue { + String _stringValue = 'test-string'; + int _intValue = 42; + double _doubleValue = 3.14; + bool _boolValue = true; + + @override + ValueSource get source => ValueSource.valueStatic; + + @override + String asString() => _stringValue; + + @override + int asInt() => _intValue; + + @override + double asDouble() => _doubleValue; + + @override + bool asBool() => _boolValue; + + // Methods to change values for tests + void setStringValue(String value) { + _stringValue = value; + } + + void setIntValue(int value) { + _intValue = value; + } + + void setDoubleValue(double value) { + _doubleValue = value; + } + + void setBoolValue(bool value) { + _boolValue = value; + } +} + +void main() { + group('RemoteConfigValueParser', () { + late RemoteConfigValueParser parser; + late MockRemoteConfigValue mockValue; + + setUp(() { + parser = RemoteConfigValueParser(); + mockValue = MockRemoteConfigValue(); + }); + + test('parses String values correctly', () { + // Arrange - direct setup + mockValue.setStringValue('test-string'); + + // Act + final result = parser.parseConfigValue(mockValue, 'STRING'); + + // Assert + expect(result, isNotNull); + expect(result!.value, equals('test-string')); + expect(result.source, equals('valueStatic')); + }); + + test('parses int values correctly', () { + // Arrange - direct setup + mockValue.setIntValue(42); + + // Act + final result = parser.parseConfigValue(mockValue, 'NUMBER'); + + // Assert + expect(result, isNotNull); + expect(result!.value, equals(42)); + expect(result.source, equals('valueStatic')); + }); + + test('parses double values correctly', () { + // Arrange - direct setup + mockValue.setDoubleValue(3.14); + + // Act + final result = parser.parseConfigValue(mockValue, 'DOUBLE'); + + // Assert + expect(result, isNotNull); + expect(result!.value, equals(3.14)); + expect(result.source, equals('valueStatic')); + }); + + test('parses boolean values correctly', () { + // Arrange - direct setup + mockValue.setBoolValue(true); + + // Act + final result = parser.parseConfigValue(mockValue, 'BOOLEAN'); + + // Assert + expect(result, isNotNull); + expect(result!.value, equals(true)); + expect(result.source, equals('valueStatic')); + }); + + test('parses JSON values correctly', () { + // Arrange - direct setup + mockValue.setStringValue('{"key":"value"}'); + + // Act + final result = + parser.parseConfigValue>(mockValue, 'JSON'); + + // Assert + expect(result, isNotNull); + expect(result!.value, isA>()); + expect(result.value['key'], equals('value')); + expect(result.source, equals('valueStatic')); + }); + + test('returns null when JSON is invalid', () { + // Arrange - direct setup + mockValue.setStringValue('invalid-json'); + + // Act + final result = + parser.parseConfigValue>(mockValue, 'JSON'); + + // Assert + expect(result, isNull); + }); + + test('returns null when valueType is unknown', () { + // Act + final result = parser.parseConfigValue(mockValue, 'UNKNOWN'); + + // Assert + expect(result, isNull); + }); + + test('returns null when valueType is null', () { + // Act + final result = parser.parseConfigValue(mockValue, null); + + // Assert + expect(result, isNull); + }); + }); +} diff --git a/package.json b/package.json index e7b380a..d75062a 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,15 @@ "python/packages/fast-api-modules/*", "python/packages/*", "python/apps/*", + "dart/apps/*", + "dart/packages/*", "infra/aws-cdk" ] }, "scripts": { "build": "dotenv -- npx turbo run build --filter @dbbs/${target:-\"*\"}", "clean:all": "npx turbo run clean && npm run clean:modules", - "clean:modules": "npx rimraf node_modules .yarn .turbo .idea", + "clean:modules": "npx rimraf node_modules .yarn .turbo .idea .venv", "cypress:open": "dotenv -- npx turbo run cypress:open --filter @dbbs/${target:-\"*\"}", "cypress:run": "dotenv -- npx turbo run cypress:run --filter @dbbs/${target:-\"*\"}", "dev": "dotenv -- npx turbo run dev --concurrency=99 --filter @dbbs/${target:-\"*\"}", @@ -38,7 +40,7 @@ "upload:settings": "./scripts/upload-settings.sh", "migrate": "dotenv -- npx turbo run migrate --concurrency=99 --filter @dbbs/${target:-\"*\"}", "test:cov": "dotenv -- npx turbo run test:cov --concurrency=99 --filter @dbbs/${target:-\"*\"}", - "prepare": "husky", + "postinstall": "husky", "check-types": "npx turbo run check-types" }, "devDependencies": { diff --git a/python/Makefile b/python/Makefile index ba53324..0ea85d7 100644 --- a/python/Makefile +++ b/python/Makefile @@ -6,11 +6,13 @@ all: setup setup: make asdf-install make check-versions + make modify-poetry-config + make install-python-deps # Installs dependencies via asdf asdf-install: - asdf plugin-list | grep -q python || asdf plugin-add python - asdf plugin-list | grep -q poetry || asdf plugin-add poetry + asdf plugin list | grep -q python || asdf plugin add python + asdf plugin list | grep -q poetry || asdf plugin add poetry asdf plugin update --all @@ -30,3 +32,11 @@ check-versions: echo "Poetry version:"; \ asdf current poetry | awk '{print $$2}' || echo "Poetry not installed"; \ ) || echo "Skipping version checks for Python and Poetry" + +# Modify poetry config +modify-poetry-config: + poetry config virtualenvs.in-project true + +# Install python dependencies +install-python-deps: + npx turbo run install:deps diff --git a/python/apps/django-server/pyproject.toml b/python/apps/django-server/pyproject.toml index 834eab1..8663f5f 100644 --- a/python/apps/django-server/pyproject.toml +++ b/python/apps/django-server/pyproject.toml @@ -27,8 +27,6 @@ dbbs-django-logger = {path = "../../packages/django-modules/dbbs_django_logger"} psycopg2-binary = "2.9.10" [tool.coverage.run] -branch = true -relative_files = true omit = ["*/migrations/*", "*/tests/*", "manage.py", "core/settings/*", "*/__init__.py"] [tool.coverage.report] diff --git a/python/apps/fast-api-server/pyproject.toml b/python/apps/fast-api-server/pyproject.toml index 4109f00..979519b 100644 --- a/python/apps/fast-api-server/pyproject.toml +++ b/python/apps/fast-api-server/pyproject.toml @@ -28,8 +28,6 @@ fast-api-db = {path = "../../packages/fast-api-modules/sqlalchemy", extras = ["p fast-api-cloudwatch = {path = "../../packages/fast-api-modules/cloudwatch"} [tool.coverage.run] -branch = true -relative_files = true omit = ["*/__init__.py"] [build-system] diff --git a/python/packages/common/package.json b/python/packages/common/package.json new file mode 100644 index 0000000..46026ab --- /dev/null +++ b/python/packages/common/package.json @@ -0,0 +1,13 @@ +{ + "name": "@dbbs/python-common", + "version": "0.1.0", + "private": true, + "description": "Common for dbbs python project", + "type": "module", + "scripts": { + "build": "poetry build", + "lint": "echo \"no lint specified\" && exit 0", + "test": "echo \"no test specified\" && exit 0", + "install:deps": "poetry install" + } +} diff --git a/python/packages/common/poetry.lock b/python/packages/common/poetry.lock new file mode 100644 index 0000000..a2b3c45 --- /dev/null +++ b/python/packages/common/poetry.lock @@ -0,0 +1,324 @@ +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.0" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "coverage-7.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dff044f661f59dace805eedb4a7404c573b6ff0cdba4a524141bc63d7be5c7fd"}, + {file = "coverage-7.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8659fd33ee9e6ca03950cfdcdf271d645cf681609153f218826dd9805ab585c"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7792f0ab20df8071d669d929c75c97fecfa6bcab82c10ee4adb91c7a54055463"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b3cd1ca7cd73d229487fa5caca9e4bc1f0bca96526b922d61053ea751fe791"}, + {file = "coverage-7.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7e128f85c0b419907d1f38e616c4f1e9f1d1b37a7949f44df9a73d5da5cd53c"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a94925102c89247530ae1dab7dc02c690942566f22e189cbd53579b0693c0783"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dcd070b5b585b50e6617e8972f3fbbee786afca71b1936ac06257f7e178f00f6"}, + {file = "coverage-7.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d50a252b23b9b4dfeefc1f663c568a221092cbaded20a05a11665d0dbec9b8fb"}, + {file = "coverage-7.6.0-cp310-cp310-win32.whl", hash = "sha256:0e7b27d04131c46e6894f23a4ae186a6a2207209a05df5b6ad4caee6d54a222c"}, + {file = "coverage-7.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dece71673b3187c86226c3ca793c5f891f9fc3d8aa183f2e3653da18566169"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7b525ab52ce18c57ae232ba6f7010297a87ced82a2383b1afd238849c1ff933"}, + {file = "coverage-7.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bea27c4269234e06f621f3fac3925f56ff34bc14521484b8f66a580aacc2e7d"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed8d1d1821ba5fc88d4a4f45387b65de52382fa3ef1f0115a4f7a20cdfab0e94"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c322ef2bbe15057bc4bf132b525b7e3f7206f071799eb8aa6ad1940bcf5fb1"}, + {file = "coverage-7.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03cafe82c1b32b770a29fd6de923625ccac3185a54a5e66606da26d105f37dac"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d1b923fc4a40c5832be4f35a5dab0e5ff89cddf83bb4174499e02ea089daf57"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4b03741e70fb811d1a9a1d75355cf391f274ed85847f4b78e35459899f57af4d"}, + {file = "coverage-7.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a73d18625f6a8a1cbb11eadc1d03929f9510f4131879288e3f7922097a429f63"}, + {file = "coverage-7.6.0-cp311-cp311-win32.whl", hash = "sha256:65fa405b837060db569a61ec368b74688f429b32fa47a8929a7a2f9b47183713"}, + {file = "coverage-7.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:6379688fb4cfa921ae349c76eb1a9ab26b65f32b03d46bb0eed841fd4cb6afb1"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f7db0b6ae1f96ae41afe626095149ecd1b212b424626175a6633c2999eaad45b"}, + {file = "coverage-7.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbdf9a72403110a3bdae77948b8011f644571311c2fb35ee15f0f10a8fc082e8"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc44bf0315268e253bf563f3560e6c004efe38f76db03a1558274a6e04bf5d5"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da8549d17489cd52f85a9829d0e1d91059359b3c54a26f28bec2c5d369524807"}, + {file = "coverage-7.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0086cd4fc71b7d485ac93ca4239c8f75732c2ae3ba83f6be1c9be59d9e2c6382"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fad32ee9b27350687035cb5fdf9145bc9cf0a094a9577d43e909948ebcfa27b"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:044a0985a4f25b335882b0966625270a8d9db3d3409ddc49a4eb00b0ef5e8cee"}, + {file = "coverage-7.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76d5f82213aa78098b9b964ea89de4617e70e0d43e97900c2778a50856dac605"}, + {file = "coverage-7.6.0-cp312-cp312-win32.whl", hash = "sha256:3c59105f8d58ce500f348c5b56163a4113a440dad6daa2294b5052a10db866da"}, + {file = "coverage-7.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca5d79cfdae420a1d52bf177de4bc2289c321d6c961ae321503b2ca59c17ae67"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d39bd10f0ae453554798b125d2f39884290c480f56e8a02ba7a6ed552005243b"}, + {file = "coverage-7.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:beb08e8508e53a568811016e59f3234d29c2583f6b6e28572f0954a6b4f7e03d"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2e16f4cd2bc4d88ba30ca2d3bbf2f21f00f382cf4e1ce3b1ddc96c634bc48ca"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6616d1c9bf1e3faea78711ee42a8b972367d82ceae233ec0ac61cc7fec09fa6b"}, + {file = "coverage-7.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad4567d6c334c46046d1c4c20024de2a1c3abc626817ae21ae3da600f5779b44"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d17c6a415d68cfe1091d3296ba5749d3d8696e42c37fca5d4860c5bf7b729f03"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9146579352d7b5f6412735d0f203bbd8d00113a680b66565e205bc605ef81bc6"}, + {file = "coverage-7.6.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cdab02a0a941af190df8782aafc591ef3ad08824f97850b015c8c6a8b3877b0b"}, + {file = "coverage-7.6.0-cp38-cp38-win32.whl", hash = "sha256:df423f351b162a702c053d5dddc0fc0ef9a9e27ea3f449781ace5f906b664428"}, + {file = "coverage-7.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:f2501d60d7497fd55e391f423f965bbe9e650e9ffc3c627d5f0ac516026000b8"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7221f9ac9dad9492cecab6f676b3eaf9185141539d5c9689d13fd6b0d7de840c"}, + {file = "coverage-7.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ddaaa91bfc4477d2871442bbf30a125e8fe6b05da8a0015507bfbf4718228ab2"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4cbe651f3904e28f3a55d6f371203049034b4ddbce65a54527a3f189ca3b390"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831b476d79408ab6ccfadaaf199906c833f02fdb32c9ab907b1d4aa0713cfa3b"}, + {file = "coverage-7.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46c3d091059ad0b9c59d1034de74a7f36dcfa7f6d3bde782c49deb42438f2450"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4d5fae0a22dc86259dee66f2cc6c1d3e490c4a1214d7daa2a93d07491c5c04b6"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07ed352205574aad067482e53dd606926afebcb5590653121063fbf4e2175166"}, + {file = "coverage-7.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:49c76cdfa13015c4560702574bad67f0e15ca5a2872c6a125f6327ead2b731dd"}, + {file = "coverage-7.6.0-cp39-cp39-win32.whl", hash = "sha256:482855914928c8175735a2a59c8dc5806cf7d8f032e4820d52e845d1f731dca2"}, + {file = "coverage-7.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:543ef9179bc55edfd895154a51792b01c017c87af0ebaae092720152e19e42ca"}, + {file = "coverage-7.6.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:6fe885135c8a479d3e37a7aae61cbd3a0fb2deccb4dda3c25f92a49189f766d6"}, + {file = "coverage-7.6.0.tar.gz", hash = "sha256:289cc803fa1dc901f84701ac10c9ee873619320f2f9aff38794db4a4a0268d51"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "mypy" +version = "1.14.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"}, + {file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"}, + {file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"}, + {file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"}, + {file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"}, + {file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"}, + {file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"}, + {file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"}, + {file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"}, + {file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"}, + {file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"}, + {file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"}, + {file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"}, + {file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"}, + {file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"}, + {file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"}, + {file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"}, + {file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"}, + {file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"}, + {file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"}, + {file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"}, + {file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"}, + {file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"}, + {file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"}, + {file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"}, + {file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"}, + {file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"}, +] + +[package.dependencies] +mypy_extensions = ">=1.0.0" +typing_extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "ruff" +version = "0.5.2" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "ruff-0.5.2-py3-none-linux_armv6l.whl", hash = "sha256:7bab8345df60f9368d5f4594bfb8b71157496b44c30ff035d1d01972e764d3be"}, + {file = "ruff-0.5.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1aa7acad382ada0189dbe76095cf0a36cd0036779607c397ffdea16517f535b1"}, + {file = "ruff-0.5.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aec618d5a0cdba5592c60c2dee7d9c865180627f1a4a691257dea14ac1aa264d"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b62adc5ce81780ff04077e88bac0986363e4a3260ad3ef11ae9c14aa0e67ef"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc42ebf56ede83cb080a50eba35a06e636775649a1ffd03dc986533f878702a3"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15c6e9f88c67ffa442681365d11df38afb11059fc44238e71a9d9f1fd51de70"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d3de9a5960f72c335ef00763d861fc5005ef0644cb260ba1b5a115a102157251"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe5a968ae933e8f7627a7b2fc8893336ac2be0eb0aace762d3421f6e8f7b7f83"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04f54a9018f75615ae52f36ea1c5515e356e5d5e214b22609ddb546baef7132"}, + {file = "ruff-0.5.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed02fb52e3741f0738db5f93e10ae0fb5c71eb33a4f2ba87c9a2fa97462a649"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3cf8fe659f6362530435d97d738eb413e9f090e7e993f88711b0377fbdc99f60"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:237a37e673e9f3cbfff0d2243e797c4862a44c93d2f52a52021c1a1b0899f846"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a2949ce7c1cbd8317432ada80fe32156df825b2fd611688814c8557824ef060"}, + {file = "ruff-0.5.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:481af57c8e99da92ad168924fd82220266043c8255942a1cb87958b108ac9335"}, + {file = "ruff-0.5.2-py3-none-win32.whl", hash = "sha256:f1aea290c56d913e363066d83d3fc26848814a1fed3d72144ff9c930e8c7c718"}, + {file = "ruff-0.5.2-py3-none-win_amd64.whl", hash = "sha256:8532660b72b5d94d2a0a7a27ae7b9b40053662d00357bb2a6864dd7e38819084"}, + {file = "ruff-0.5.2-py3-none-win_arm64.whl", hash = "sha256:73439805c5cb68f364d826a5c5c4b6c798ded6b7ebaa4011f01ce6c94e4d5583"}, + {file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"}, +] + +[[package]] +name = "setuptools" +version = "70.3.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "setuptools-70.3.0-py3-none-any.whl", hash = "sha256:fe384da74336c398e0d956d1cae0669bc02eed936cdb1d49b57de1990dc11ffc"}, + {file = "setuptools-70.3.0.tar.gz", hash = "sha256:f171bab1dfbc86b132997f26a119f6056a57950d058587841a0082e8830f9dc5"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-ruff (>=0.3.2) ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[metadata] +lock-version = "2.1" +python-versions = "3.11.6" +content-hash = "61d63b85f0b2515c30a47bee2b4f0f85498eb099ff04c1952233126f97fa6ac6" diff --git a/python/packages/fast-api-modules/cloudwatch/package.json b/python/packages/fast-api-modules/cloudwatch/package.json index 1a3a075..6778419 100644 --- a/python/packages/fast-api-modules/cloudwatch/package.json +++ b/python/packages/fast-api-modules/cloudwatch/package.json @@ -1,5 +1,5 @@ { - "name": "@dbbs/fast-api-cloudwatch", + "name": "@dbbs/fastapi-cloudwatch", "version": "1.0.0", "private": true, "description": "FastAPI cloudwatch module", diff --git a/python/packages/fast-api-modules/sqlalchemy/package.json b/python/packages/fast-api-modules/sqlalchemy/package.json index a38f56e..8288f82 100644 --- a/python/packages/fast-api-modules/sqlalchemy/package.json +++ b/python/packages/fast-api-modules/sqlalchemy/package.json @@ -1,5 +1,5 @@ { - "name": "@dbbs/fast-api-db", + "name": "@dbbs/fastapi-db", "version": "0.1.0", "private": true, "description": "FastAPI DB module", diff --git a/sonar-project.properties b/sonar-project.properties index c3f04f0..ad736ad 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -9,46 +9,22 @@ sonar.host.url=https://sonarcloud.io sonar.project.monorepo.enabled=true # This is the relative path to your source directories -sonar.sources=apps/server-api, \ - apps/serverless-api, \ - apps/serverless-settings-service, \ - apps/web-spa, \ - apps/web-ssr, \ - apps/mobile-app, \ - apps/django-server, \ - packages/common, \ - packages/cypress, \ - packages/eslint-config, \ - packages/feature-config, \ - packages/jest-config, \ - packages/mobile-components, \ - packages/mui-components, \ - packages/nestjs-modules/auth-jwt, \ - packages/nestjs-modules/authz, \ - packages/nestjs-modules/decorators, \ - packages/nestjs-modules/health, \ - packages/nestjs-modules/healthcheck, \ - packages/nestjs-modules/logger, \ - packages/s3-log-transport, \ - packages/settings, \ - packages/tsconfig, \ - packages/web-features, \ - packages/django-modules +sonar.sources=python, typescript # Path to the LCOV report for test coverage -sonar.javascript.lcov.reportPaths=apps/**/coverage/lcov.info, packages/**/coverage/lcov.info +sonar.javascript.lcov.reportPaths=typescript/apps/**/coverage/lcov.info, typescript/packages/**/coverage/lcov.info # Additional configuration for TypeScript # Path to your TypeScript files -sonar.typescript.lcov.reportPaths=apps/**/coverage/lcov.info, packages/**/coverage/lcov.info +sonar.typescript.lcov.reportPaths=typescript/apps/**/coverage/lcov.info, typescript/packages/**/coverage/lcov.info # Additional configuration for Python -sonar.python.coverage.reportPaths=apps/**/coverage.xml, packages/django-modules/**/coverage.xml -sonar.python.sources=apps/django-server, \ - packages/django-modules -sonar.python.version=3.12 +sonar.python.coverage.reportPaths=python/apps/**/coverage.xml, \ + python/packages/**/**/coverage.xml, \ + +sonar.python.version=3.11.6 # Test file pattern -# sonar.test.inclusions=**/*.spec.ts, **/*.e2e-spec.ts +sonar.test.inclusions=**/*.spec.ts, **/*.e2e-spec.ts # (Optional) Path to tsconfig.json if you need to override default sonar.typescript.tsconfigPath=tsconfig.json @@ -87,7 +63,7 @@ sonar.exclusions=node_modules/**, \ **/settings/**, \ **/migrations/**, \ **/__init__.py, \ - apps/strapi/**, \ + typescript/apps/strapi/**, \ **/tenants/page.tsx diff --git a/typescript/Makefile b/typescript/Makefile index 70ac72f..46fdd57 100644 --- a/typescript/Makefile +++ b/typescript/Makefile @@ -10,9 +10,9 @@ setup: # Installs dependencies via asdf asdf-install: - asdf plugin-list | grep -q nodejs || asdf plugin-add nodejs - asdf plugin-list | grep -q ruby || asdf plugin-add ruby - asdf plugin-list | grep -q cocoapods || asdf plugin-add cocoapods + asdf plugin list | grep -q nodejs || asdf plugin add nodejs + asdf plugin list | grep -q ruby || asdf plugin add ruby + asdf plugin list | grep -q cocoapods || asdf plugin add cocoapods asdf plugin update --all diff --git a/typescript/apps/admin-panel-web-spa/package.json b/typescript/apps/admin-panel-web-spa/package.json index e92dd7e..19aaf55 100644 --- a/typescript/apps/admin-panel-web-spa/package.json +++ b/typescript/apps/admin-panel-web-spa/package.json @@ -9,7 +9,7 @@ "dev": "npx vite", "lint": "npx eslint . --ext ts,tsx --max-warnings 999 --fix", "preview": "npx vite preview", - "test": "npx jest --coverage --verbose" + "test": "npx jest --coverage --verbose --runInBand" }, "dependencies": { "@dbbs/mui-components": "*", diff --git a/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/geos.slice.ts b/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/geos.slice.ts index e214434..1da10b2 100644 --- a/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/geos.slice.ts +++ b/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/geos.slice.ts @@ -1,4 +1,4 @@ -import { Geo, GeoProperties, ListPayload, ListResponse, UpdatePayload } from '../../../types' +import { Geo, ListPayload, ListResponse, UpdatePayload } from '../../../types' import { buildQueryParams } from '../../../utils' import { apiSlice, ApiSliceTag } from '../apiSlice' @@ -48,9 +48,6 @@ export const geoSlice = apiSlice.injectEndpoints({ }), getGeoById: builder.query({ query: buildGeosIdPath - }), - getGeoProperties: builder.query({ - query: buildGeosPropertiesPath }) }) }) @@ -59,7 +56,6 @@ export const selectDomainGetGeoList = geoSlice.endpoints.getGeoList.select export const { useGetGeoListQuery, - useGetGeoPropertiesQuery, useCreateGeoMutation, useUpdateGeoMutation, useDeleteGeoMutation, diff --git a/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/mocks.ts b/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/mocks.ts index 5549263..3fe90e7 100644 --- a/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/mocks.ts +++ b/typescript/apps/admin-panel-web-spa/src/data-access/domain/geosSlice/mocks.ts @@ -7,7 +7,6 @@ export const mockGeo: Geo = { createdBy: 'admin_user_tutorial-99246_test', name: 'Onslow County School Board', scheduleUrl: 'https://go.boarddocs.com/nc/onslow/Board.nsf/public', - geoType: 'School board', timezone: 'America/New_York', captureScheduleFlag: true, captureStreamFlag: false, @@ -17,7 +16,10 @@ export const mockGeo: Geo = { parent: { id: '1', name: 'Geo', - fullName: 'Geo (Other) in The Universe' + bubbleId: '', + scheduleUrl: '', + detectStartMethod: '', + detectEndMethod: '' }, channelUrl: 'https://www.youtube.com/@OCSMultimedia/streams', diff --git a/typescript/apps/admin-panel-web-spa/src/data-access/domain/meetingTypeSlice/mocks.ts b/typescript/apps/admin-panel-web-spa/src/data-access/domain/meetingTypeSlice/mocks.ts index c89e399..11d624a 100644 --- a/typescript/apps/admin-panel-web-spa/src/data-access/domain/meetingTypeSlice/mocks.ts +++ b/typescript/apps/admin-panel-web-spa/src/data-access/domain/meetingTypeSlice/mocks.ts @@ -2,14 +2,9 @@ import { ListResponse, MeetingType, UpdatePayload } from '../../../types' export const mockMeetingType: MeetingType = { id: '1696363998091x841233251333633000', - modifiedDate: '2023-11-09T18:50:52.743000Z', - createdDate: '2023-10-03T20:13:18.096000Z', - createdBy: 'admin_user_meta_live', - title: 'Wynwood Design Review Committee', - sortkey: 'Miami1Wynwood Design Review Committee', - geo: '1686087434936x926623722684097400', - geoParent: '1686087434934x882269048954331000', - geoParent2: '1698294322096x146294304468672640' + name: 'Wynwood Design Review Committee', + createdAt: '2021-07-29T17:17:25.000Z', + updatedAt: '2021-07-29T17:17:25.000Z' } export const mockMeetingTypeListResponse: ListResponse = { diff --git a/typescript/apps/admin-panel-web-spa/src/feature/GeoConfig/GeoConfig.tsx b/typescript/apps/admin-panel-web-spa/src/feature/GeoConfig/GeoConfig.tsx new file mode 100644 index 0000000..c66d012 --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/GeoConfig/GeoConfig.tsx @@ -0,0 +1,165 @@ +import React, { FC, useEffect, useMemo, useState, useCallback } from 'react' +import { FieldValues, useForm } from 'react-hook-form' +import { useNavigate, useParams } from '@tanstack/react-router' +import { skipToken } from '@reduxjs/toolkit/query' +import { debounce } from '@dbbs/mui-components' +import { + useCreateGeoMutation, + useGetGeoByIdQuery, + useGetGeoListQuery, + useGetMeetingTypeListQuery, + useUpdateGeoMutation +} from '../../data-access' +import { GeoConfigForm } from '../../ui' +import { displayValidationErrors } from '../../utils' +import { Geo, ListPayload } from '../../types' + +const defaultValues: Geo = { + id: '', + bubbleId: '', + name: '', + scheduleUrl: '', + detectStartMethod: '', + detectEndMethod: '', + statusSchedule: '', + timezone: '', + jurisdiction: false, + channelUrl: '', + scheduleFormat: '', + statusStream: '', + flagOnlyAgenda: false, + flagOptInOnly: false, + singlePlayerUrl: '', + flagLive: false, + detectEndOcrString: '', + debug: false, + demo: false, + streamType: '' +} + +export const GeoEditPage: FC = () => { + const navigate = useNavigate({ from: '/geo/create' }) + + const [updateGeo, { isLoading: isUpdateLoading }] = useUpdateGeoMutation() + const [createGeo, { isLoading: isCreateGeoLoading }] = useCreateGeoMutation() + + const { geoId } = useParams({ strict: false }) + + const { data: defaultGeo } = useGetGeoByIdQuery(geoId || skipToken) + + const geo = useMemo( + () => ({ + ...defaultGeo, + parent: { + name: defaultGeo?.parent?.name + } + }), + [defaultGeo] + ) + + const [parentSearchTerm, setParentSearchTerm] = useState('') + const [meetingTypeSearchTerm, setMeetingTypeSearchTerm] = useState('') + + const baseOptionsQuery: ListPayload = { + limit: 10, + offset: 0, + filterField: 'name', + filterOperator: 'contains' + } + + const { + data: parentData, + isLoading: isParentLoading, + isFetching: isParentFetching + } = useGetGeoListQuery({ ...baseOptionsQuery, filterValue: parentSearchTerm }) + + const { + data: meetingTypeData, + isLoading: isMeetingTypeLoading, + isFetching: isMeetingTypeFetching + } = useGetMeetingTypeListQuery({ ...baseOptionsQuery, filterValue: meetingTypeSearchTerm }) + + const handleParentSearchTermChange = useCallback((searchText: string) => { + debounce(() => { + setParentSearchTerm(searchText) + }, 300) + }, []) + + const handleMeetingTypeSearchTermChange = useCallback((searchText: string) => { + debounce(() => { + setMeetingTypeSearchTerm(searchText) + }, 300) + }, []) + + const parentSearchOptions = + parentData?.results.map(({ id, name }) => ({ + id, + name + })) || [] + + const meetingTypeSearchOptions = + meetingTypeData?.results.map(({ id, name }) => ({ + id, + name + })) || [] + + const { + handleSubmit, + control, + formState: { errors }, + reset + } = useForm({ + defaultValues + }) + + useEffect(() => { + if (geo) { + reset(geo) + } + }, [geo, reset]) + + const onSubmit = (item: FieldValues) => { + const { parent, meetingType, ...validGeo } = item + + if (!geoId) { + createGeo({ + ...validGeo, + geoParentId: item.parent?.id ? Number(item.parent?.id) : undefined, + meetingTypeId: item.meetingType?.id ? Number(item.meetingType?.id) : undefined, + scheduleRefreshFrequency: Number(item.scheduleRefreshFrequency) + }) + .unwrap() + .then(({ id }) => { + navigate({ to: `/geo/${id}/edit` }) + }) + .catch(displayValidationErrors) + } else { + updateGeo({ + id: geoId, + item: { + ...validGeo, + geoParentId: item.parent?.id ? Number(item.parent?.id) : undefined, + meetingTypeId: item.meetingType?.id ? Number(item.meetingType?.id) : undefined, + scheduleRefreshFrequency: Number(item.scheduleRefreshFrequency) + } + }) + .unwrap() + .catch(displayValidationErrors) + } + } + + return ( + + ) +} diff --git a/typescript/apps/admin-panel-web-spa/src/feature/GeoConfig/index.ts b/typescript/apps/admin-panel-web-spa/src/feature/GeoConfig/index.ts new file mode 100644 index 0000000..39df7c2 --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/GeoConfig/index.ts @@ -0,0 +1 @@ +export * from './GeoConfig' diff --git a/typescript/apps/admin-panel-web-spa/src/feature/GeoList/GeoList.spec.tsx b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/GeoList.spec.tsx new file mode 100644 index 0000000..e3f2840 --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/GeoList.spec.tsx @@ -0,0 +1,137 @@ +import React from 'react' +import { render, screen, waitFor, within } from '@testing-library/react' +import { Provider } from 'react-redux' +import userEvent from '@testing-library/user-event' + +import { GeoList } from './GeoList' +import { mockGeo } from '../../data-access' +import { setupStore } from '../../testUtils/store' +import { DATA_TABLE_TEST_IDS, DATA_TABLE_TOOLBAR_TEST_IDS } from '../../ui' +import { rootReducer } from '../../store/reducer' + +const mockNavigate = jest.fn() + +jest.mock('@tanstack/react-router', () => ({ + ...jest.requireActual('@tanstack/react-router'), + useNavigate: jest.fn(() => mockNavigate), + useRouterState: jest.fn(() => ({ + location: { pathname: '/' } + })), + Link: ({ to, ...props }: any) => +})) + +jest.mock('@mui/x-data-grid', () => ({ + ...jest.requireActual('@mui/x-data-grid'), + useGridApiRef: jest.fn(() => ({ current: { exportState: jest.fn(() => ({})) } })), + useGridRowSelectionModel: jest.fn(() => ({ selectedIds: [] })), + useGridRowModesModel: jest.fn(() => ({})) +})) + +const { store } = setupStore({ overrideReducer: rootReducer }) + +const setup = () => + render( + + + + ) + +const newViewName = '1' + +describe('', () => { + it('should render data correctly', async () => { + setup() + + const createViewInput = await screen.findByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_INPUT) + expect(createViewInput).toBeVisible() + expect(createViewInput).toBeEnabled() + + const createViewButton = await screen.findByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_BUTTON) + expect(createViewButton).toBeVisible() + expect(createViewButton).toBeEnabled() + expect(createViewButton).toHaveTextContent('Create View') + + expect(screen.getByTestId(DATA_TABLE_TEST_IDS.VIEWS)).toBeVisible() + + expect(screen.getByTestId(DATA_TABLE_TEST_IDS.getViewTestId('Default'))).toBeVisible() + + const rows = screen.getAllByRole('row') + expect(rows.length).toBe(1) + }) + + it('should add new view', async () => { + setup() + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()) + + const createViewInput = within(screen.getByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_INPUT)).getByPlaceholderText( + 'New view name' + ) + await userEvent.type(createViewInput, newViewName) + + const createViewButton = screen.getByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_BUTTON) + await userEvent.click(createViewButton) + + const newViewElement = await screen.findByTestId(DATA_TABLE_TEST_IDS.getViewTestId(newViewName)) + expect(newViewElement).toBeVisible() + expect(newViewElement).toBeEnabled() + expect(newViewElement).toHaveTextContent(newViewName) + }) + + it('should delete view', async () => { + setup() + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()) + + const createViewInput = within(screen.getByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_INPUT)).getByPlaceholderText( + 'New view name' + ) + await userEvent.type(createViewInput, newViewName) + + const createViewButton = screen.getByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_BUTTON) + await userEvent.click(createViewButton) + + await waitFor(() => { + const newViewElement = screen.getByTestId(DATA_TABLE_TEST_IDS.getViewTestId(newViewName)) + expect(newViewElement).toBeVisible() + }) + + const deleteViewButton = screen.getByTestId(DATA_TABLE_TEST_IDS.getDeleteViewButtonTestId(newViewName)) + await userEvent.click(deleteViewButton) + + await waitFor(() => + expect(screen.queryByTestId(DATA_TABLE_TEST_IDS.getViewTestId(newViewName))).not.toBeInTheDocument() + ) + }) + + it('should redirect to config page on add new row', async () => { + setup() + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()) + + const addButton = await screen.findByTestId(DATA_TABLE_TOOLBAR_TEST_IDS.ADD_BUTTON) + await userEvent.click(addButton) + + await waitFor(() => + expect(mockNavigate).toHaveBeenCalledWith({ + to: '/geo/create' + }) + ) + }) + + it('should redirect to config page on edit click', async () => { + setup() + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()) + + const editButton = await screen.findByTestId(DATA_TABLE_TEST_IDS.getEditButtonTestId(mockGeo.id)) + + await userEvent.click(editButton) + + await waitFor(() => + expect(mockNavigate).toHaveBeenCalledWith({ + to: '/geo/create' + }) + ) + }) +}) diff --git a/typescript/apps/admin-panel-web-spa/src/feature/GeoList/GeoList.tsx b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/GeoList.tsx new file mode 100644 index 0000000..2a04119 --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/GeoList.tsx @@ -0,0 +1,166 @@ +import React, { FC, useEffect, useState } from 'react' +import { + GridInitialState, + GridRowId, + GridRowModes, + GridRowModesModel, + useGridApiRef, + debounce +} from '@dbbs/mui-components' +import { useDispatch, useSelector } from 'react-redux' +import { useNavigate } from '@tanstack/react-router' +import { DataTable } from '../../ui' +import { + DEFAULT_STATE, + deleteView, + saveView, + selectAllViews, + selectCurrentView, + selectGridState, + setCurrentView, + setGridState, + useDeleteGeoMutation, + useGetGeoListQuery +} from '../../data-access' +import 'react-toastify/dist/ReactToastify.css' +import { getGeoColumnModel } from './utils' +import { buildListQuery, displayValidationErrors, renderDefaultActions } from '../../utils' + +const PAGE_FILTER_KEY = 'GEO' + +export const GeoList: FC = () => { + const [rowModesModel, setRowModesModel] = useState({}) + + const navigate = useNavigate({ from: '/geo' }) + + const dispatch = useDispatch() + + const gridApiRef = useGridApiRef() + + const initialState = useSelector(selectGridState(PAGE_FILTER_KEY)) + + const availableViews = useSelector(selectAllViews(PAGE_FILTER_KEY)) + + const currentView = useSelector(selectCurrentView(PAGE_FILTER_KEY)) + + const currentState = gridApiRef.current.exportState ? gridApiRef.current.exportState() : initialState + + const query = buildListQuery(currentState) + + const { data, isLoading, isFetching } = useGetGeoListQuery(query) + + const [deleteGeo, { isLoading: isDeleteLoading }] = useDeleteGeoMutation() + + const geoList = data?.results + + const tableColumns = getGeoColumnModel() + + const isTableLoading = isLoading || isFetching || isDeleteLoading + + const handleDeleteClick = (idToDelete: string) => { + deleteGeo(idToDelete).unwrap().catch(displayValidationErrors) + } + + const handleEditClick = (rowId: GridRowId) => { + navigate({ to: `/geo/${rowId}/edit` }) + } + + const handleSaveClick = (rowId: GridRowId) => { + setRowModesModel({ ...rowModesModel, [rowId]: { mode: GridRowModes.View } }) + } + + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel) + } + + const handleAddClick = () => { + navigate({ to: '/geo/create' }) + } + + const debouncedHandleStateChange = debounce((state: GridInitialState) => { + dispatch(setGridState({ key: PAGE_FILTER_KEY, state })) + if (currentView) { + dispatch( + saveView({ + key: PAGE_FILTER_KEY, + viewName: currentView.name, + state: { ...state, isDefaultView: currentView.name === DEFAULT_STATE } + }) + ) + } + }, 100) + + const handleStateChange = () => { + const state = gridApiRef.current.exportState() + debouncedHandleStateChange(state) + } + + const handleCreateView = (viewToCreate: string) => { + const defaultState = availableViews.find(({ viewName }) => viewName === DEFAULT_STATE) + + dispatch( + saveView({ + key: PAGE_FILTER_KEY, + viewName: viewToCreate, + state: { ...(defaultState?.view || gridApiRef.current.exportState()), isDefaultView: false } + }) + ) + } + + const handleChangeView = (viewToSelect: string) => { + dispatch(setCurrentView({ key: PAGE_FILTER_KEY, viewName: viewToSelect })) + } + + const handleDeleteView = (viewName: string) => { + if (viewName === currentView.name) dispatch(setCurrentView({ key: PAGE_FILTER_KEY, viewName: DEFAULT_STATE })) + dispatch(deleteView({ key: PAGE_FILTER_KEY, viewName })) + } + + const { cursor, count, remaining } = data || {} + const totalCount = (cursor || 0) + (count || 0) + (remaining || 0) + + useEffect(() => { + if (!availableViews?.length) { + const stateToSave = gridApiRef.current.exportState() + dispatch( + saveView({ key: PAGE_FILTER_KEY, viewName: DEFAULT_STATE, state: { ...stateToSave, isDefaultView: true } }) + ) + } + }, [availableViews?.length, dispatch, gridApiRef]) + + useEffect(() => { + if (currentView && currentView.state) { + gridApiRef.current.restoreState(currentView.state) + } + }, [currentView, currentView.name, currentView.state, gridApiRef]) + + return ( + + renderDefaultActions({ + params, + rowModesModel, + handleSaveClick, + handleEditClick, + handleDeleteClick + }) + } + rowModesModel={rowModesModel} + handleRowModesModelChange={handleRowModesModelChange} + handleAddClick={handleAddClick} + handleStateChange={handleStateChange} + initialState={initialState} + gridApiRef={gridApiRef} + views={availableViews} + currentView={currentView.name || DEFAULT_STATE} + onChangeView={handleChangeView} + onCreateView={handleCreateView} + onDeleteView={handleDeleteView} + loading={isTableLoading} + /> + ) +} diff --git a/typescript/apps/admin-panel-web-spa/src/feature/GeoList/index.ts b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/index.ts new file mode 100644 index 0000000..1668668 --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/index.ts @@ -0,0 +1 @@ +export * from './GeoList' diff --git a/typescript/apps/admin-panel-web-spa/src/feature/GeoList/utils.tsx b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/utils.tsx new file mode 100644 index 0000000..a8f817b --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/GeoList/utils.tsx @@ -0,0 +1,87 @@ +import React from 'react' +import { GridColDef, GridRenderCellParams } from '@dbbs/mui-components' +import moment from 'moment-timezone' +import { Link } from '@tanstack/react-router' + +export const getGeoColumnModel = (): GridColDef[] => [ + { field: 'id', headerName: 'ID', editable: false }, + { field: 'bubbleId', headerName: 'Bubble ID', width: 250, editable: false }, + { field: 'name', headerName: 'Name', width: 250, editable: false }, + { + field: 'geoType', + headerName: 'Geo Type', + width: 150, + editable: false + }, + { + field: 'timezone', + headerName: 'Time Zone', + width: 200, + type: 'singleSelect', + valueOptions: moment.tz.names(), + editable: false + }, + { field: 'createdBy', headerName: 'Created By', width: 250, editable: false }, + { + field: 'createdDate', + headerName: 'Created Date', + type: 'dateTime', + valueGetter: (value: string) => new Date(value), + width: 200, + editable: false + }, + { + field: 'modifiedDate', + headerName: 'Modified Date', + type: 'dateTime', + valueGetter: (value: string) => new Date(value), + width: 200, + editable: false + }, + { field: 'scheduleUrl', headerName: 'Schedule URL', width: 350, editable: false }, + { + field: 'captureScheduleFlag', + headerName: 'Capture Schedule', + type: 'boolean', + width: 150, + editable: false + }, + { field: 'captureStreamFlag', headerName: 'Capture Stream', type: 'boolean', width: 150, editable: false }, + { field: 'scheduleFormat', headerName: 'Schedule Format', width: 150, editable: false }, + { field: 'streamType', headerName: 'Stream Type', width: 150, editable: false }, + { + field: 'jurisdiction', + headerName: 'Jurisdiction', + width: 100, + type: 'boolean', + editable: false + }, + { + editable: false, + field: 'parent', + headerName: 'Parent', + width: 300, + renderCell: (params: GridRenderCellParams) => ( + + {params.row?.parent?.fullName || params.row?.parent?.name || ''} + + ) + }, + { field: 'sortKey', headerName: 'Sort Key', width: 300, editable: false }, + { field: 'channelUrl', headerName: 'Channel URL', width: 300, editable: false }, + { field: 'statusSchedule', headerName: 'Schedule Status', width: 150, editable: false }, + { field: 'mirrorGeo', headerName: 'Mirror Geo ID', width: 300, editable: false }, + { field: 'bubbleId', headerName: 'Bubble ID', width: 150, editable: false }, + { field: 'geoParentId', headerName: 'Geo Parent ID', width: 150, editable: false }, + { field: 'detectStartMethod', headerName: 'Detect Start Method', width: 200, editable: false }, + { field: 'detectEndMethod', headerName: 'Detect End Method', width: 200, editable: false }, + { field: 'statusStream', headerName: 'Status Stream', width: 150, editable: false }, + { field: 'flagOnlyAgenda', headerName: 'Flag Only Agenda', type: 'boolean', width: 150, editable: false }, + { field: 'flagOptInOnly', headerName: 'Flag Opt-In Only', type: 'boolean', width: 150, editable: false }, + { field: 'singlePlayerUrl', headerName: 'Single Player URL', width: 300, editable: false }, + { field: 'flagLive', headerName: 'Flag Live', type: 'boolean', width: 150, editable: false }, + { field: 'detectEndOcrString', headerName: 'Detect End OCR String', width: 200, editable: false }, + { field: 'debug', headerName: 'Debug', type: 'boolean', width: 100, editable: false }, + { field: 'demo', headerName: 'Demo', type: 'boolean', width: 100, editable: false }, + { field: 'scheduleRefreshFrequency', headerName: 'Schedule Refresh Frequency', width: 200, editable: false } +] diff --git a/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/MeetingType.spec.tsx b/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/MeetingType.spec.tsx new file mode 100644 index 0000000..0f881cb --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/MeetingType.spec.tsx @@ -0,0 +1,107 @@ +import React from 'react' +import { render, screen, waitFor, within } from '@testing-library/react' +import { Provider } from 'react-redux' +import userEvent from '@testing-library/user-event' + +import { MeetingTypeList } from './MeetingType' +import { setupStore } from '../../testUtils/store' +import { DATA_TABLE_TEST_IDS } from '../../ui' +import { rootReducer } from '../../store/reducer' + +jest.mock('@tanstack/react-router', () => ({ + ...jest.requireActual('@tanstack/react-router'), + useNavigate: jest.fn(() => jest.fn()), + useRouterState: jest.fn(() => ({ + location: { pathname: '/' } + })), + Link: ({ to, ...props }: any) => +})) + +jest.mock('@mui/x-data-grid', () => ({ + ...jest.requireActual('@mui/x-data-grid'), + useGridApiRef: jest.fn(() => ({ current: { exportState: jest.fn(() => ({})) } })), + useGridRowSelectionModel: jest.fn(() => ({ selectedIds: [] })), + useGridRowModesModel: jest.fn(() => ({})) +})) + +const { store } = setupStore({ overrideReducer: rootReducer }) + +const setup = () => + render( + + + + ) + +const newViewName = 'New View' + +describe('', () => { + it('should render data correctly', async () => { + setup() + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()) + + const createViewInput = await screen.findByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_INPUT) + expect(createViewInput).toBeVisible() + expect(createViewInput).toBeEnabled() + + const createViewButton = await screen.findByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_BUTTON) + expect(createViewButton).toBeVisible() + expect(createViewButton).toBeEnabled() + expect(createViewButton).toHaveTextContent('Create View') + + expect(screen.getByTestId(DATA_TABLE_TEST_IDS.VIEWS)).toBeVisible() + + expect(screen.getByTestId(DATA_TABLE_TEST_IDS.getViewTestId('Default'))).toBeVisible() + + const rows = screen.getAllByRole('row') + expect(rows.length).toBe(2) + }) + + it('should add new view', async () => { + setup() + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()) + + const createViewInput = within(screen.getByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_INPUT)).getByPlaceholderText( + 'New view name' + ) + await userEvent.type(createViewInput, newViewName) + + const createViewButton = await screen.findByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_BUTTON) + await userEvent.click(createViewButton) + + await waitFor(() => { + const newViewElement = screen.getByTestId(DATA_TABLE_TEST_IDS.getViewTestId(newViewName)) + expect(newViewElement).toBeVisible() + expect(newViewElement).toBeEnabled() + expect(newViewElement).toHaveTextContent(newViewName) + }) + }) + + it('should delete view', async () => { + setup() + + await waitFor(() => expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()) + + const createViewInput = within(screen.getByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_INPUT)).getByPlaceholderText( + 'New view name' + ) + await userEvent.type(createViewInput, newViewName) + + const createViewButton = screen.getByTestId(DATA_TABLE_TEST_IDS.CREATE_VIEW_BUTTON) + await userEvent.click(createViewButton) + + await waitFor(() => { + const newViewElement = screen.getByTestId(DATA_TABLE_TEST_IDS.getViewTestId(newViewName)) + expect(newViewElement).toBeVisible() + }) + + const deleteViewButton = screen.getByTestId(DATA_TABLE_TEST_IDS.getDeleteViewButtonTestId(newViewName)) + await userEvent.click(deleteViewButton) + + await waitFor(() => + expect(screen.queryByTestId(DATA_TABLE_TEST_IDS.getViewTestId(newViewName))).not.toBeInTheDocument() + ) + }) +}) diff --git a/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/MeetingType.tsx b/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/MeetingType.tsx new file mode 100644 index 0000000..6cd158c --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/MeetingType.tsx @@ -0,0 +1,223 @@ +import React, { FC, useEffect, useState } from 'react' +import { + GridColDef, + GridInitialState, + GridRowId, + GridRowModes, + GridRowModesModel, + useGridApiRef, + debounce +} from '@dbbs/mui-components' +import { toast } from 'react-toastify' +import { useDispatch, useSelector } from 'react-redux' +import { DataTable } from '../../ui' +import { + DEFAULT_STATE, + deleteView, + mockMeetingType, + saveView, + selectAllViews, + selectCurrentView, + selectGridState, + setCurrentView, + setGridState, + useCreateMeetingTypeMutation, + useDeleteMeetingTypeMutation, + useGetMeetingTypeListQuery, + useUpdateMeetingTypeMutation +} from '../../data-access' +import { MeetingType } from '../../types' +import 'react-toastify/dist/ReactToastify.css' +import { BASE_EDITABLE_FIELD, buildListQuery, displayValidationErrors, renderDefaultActions } from '../../utils' + +const NEW_ROW_ID = 'NEW' +const PAGE_FILTER_KEY = 'MEETING_TYPE' + +const TABLE_COLUMNS: GridColDef[] = [ + { ...BASE_EDITABLE_FIELD, field: 'id', headerName: 'ID', width: 200 }, + { ...BASE_EDITABLE_FIELD, field: 'name', headerName: 'Name' }, + { + ...BASE_EDITABLE_FIELD, + field: 'createdAt', + headerName: 'Created At', + type: 'dateTime', + valueGetter: (value: string) => new Date(value), + width: 250 + }, + { + ...BASE_EDITABLE_FIELD, + field: 'updatedAt', + headerName: 'Updated At', + type: 'dateTime', + valueGetter: (value: string) => new Date(value), + width: 250 + } +] + +export const MeetingTypeList: FC = () => { + const [rowModesModel, setRowModesModel] = useState({}) + const [newRow, setNewRow] = useState<(Partial & { isNew: boolean }) | null>(null) + + const dispatch = useDispatch() + + const gridApiRef = useGridApiRef() + + const initialState = useSelector(selectGridState(PAGE_FILTER_KEY)) + + const availableViews = useSelector(selectAllViews(PAGE_FILTER_KEY)) + + const currentView = useSelector(selectCurrentView(PAGE_FILTER_KEY)) + + const currentState = gridApiRef.current.exportState ? gridApiRef.current.exportState() : initialState + + const query = buildListQuery(currentState) + + const { data, isLoading, isFetching } = useGetMeetingTypeListQuery(query) + + console.log({ data }) + + const [createMeetingType, { isLoading: isCreateLoading }] = useCreateMeetingTypeMutation() + const [updateMeetingType, { isLoading: isUpdateLoading }] = useUpdateMeetingTypeMutation() + const [deleteMeetingType, { isLoading: isDeleteLoading }] = useDeleteMeetingTypeMutation() + + const typeList = data?.results + + const isTableLoading = isLoading || isFetching || isCreateLoading || isUpdateLoading || isDeleteLoading + + const handleDeleteClick = (idToDelete: string) => { + deleteMeetingType(idToDelete) + .unwrap() + .catch(({ error }) => toast.error(`Delete Meeting Type Error: ${error}`)) + } + + const handleEditClick = (rowId: GridRowId) => { + setRowModesModel({ ...rowModesModel, [rowId]: { mode: GridRowModes.Edit } }) + } + + const handleSaveClick = (rowId: GridRowId) => { + setRowModesModel({ ...rowModesModel, [rowId]: { mode: GridRowModes.View } }) + } + + const handleCancelClick = (rowId: GridRowId) => { + if (rowId === NEW_ROW_ID) { + setNewRow(null) + } + setRowModesModel({ + ...rowModesModel, + [rowId]: { mode: GridRowModes.View, ignoreModifications: true } + }) + } + + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel) + } + + const onRowUpdate = (updatedRow: MeetingType & { isNew?: boolean }, isNewRow: boolean) => { + const { id, isNew, ...meetingTypeToValidate } = updatedRow + + if (isNewRow) { + createMeetingType(meetingTypeToValidate).unwrap().catch(displayValidationErrors) + setNewRow(null) + } else { + const { ...meetingTypeToUpdate } = meetingTypeToValidate + updateMeetingType({ id, item: meetingTypeToUpdate }).unwrap().catch(displayValidationErrors) + } + } + + const handleAddClick = () => { + setNewRow({ id: NEW_ROW_ID, isNew: true }) + setRowModesModel({ + ...rowModesModel, + [NEW_ROW_ID]: { mode: GridRowModes.Edit } + }) + } + + const debouncedHandleStateChange = debounce((state: GridInitialState) => { + dispatch(setGridState({ key: PAGE_FILTER_KEY, state })) + if (currentView) { + dispatch( + saveView({ + key: PAGE_FILTER_KEY, + viewName: currentView.name, + state: { ...state, isDefaultView: currentView.name === DEFAULT_STATE } + }) + ) + } + }, 100) + + const handleStateChange = () => { + const state = gridApiRef.current.exportState() + debouncedHandleStateChange(state) + } + + const handleCreateView = (viewToCreate: string) => { + const defaultState = availableViews.find(({ viewName }) => viewName === DEFAULT_STATE) + + dispatch( + saveView({ + key: PAGE_FILTER_KEY, + viewName: viewToCreate, + state: { ...(defaultState?.view || gridApiRef.current.exportState()), isDefaultView: false } + }) + ) + } + + const handleChangeView = (viewToSelect: string) => { + dispatch(setCurrentView({ key: PAGE_FILTER_KEY, viewName: viewToSelect })) + } + + const handleDeleteView = (viewName: string) => { + if (viewName === currentView.name) dispatch(setCurrentView({ key: PAGE_FILTER_KEY, viewName: DEFAULT_STATE })) + dispatch(deleteView({ key: PAGE_FILTER_KEY, viewName })) + } + + const filteredRows = newRow ? [newRow as MeetingType, ...(typeList || [])] : typeList + + const { cursor, count, remaining } = data || {} + const totalCount = (cursor || 0) + (count || 0) + (remaining || 0) + + useEffect(() => { + if (!availableViews?.length) { + const stateToSave = gridApiRef.current.exportState() + dispatch( + saveView({ key: PAGE_FILTER_KEY, viewName: DEFAULT_STATE, state: { ...stateToSave, isDefaultView: true } }) + ) + } + }, []) + + useEffect(() => { + if (currentView && currentView.state) gridApiRef.current.restoreState(currentView.state) + }, [currentView, currentView.name, currentView.state, gridApiRef]) + + return ( + + renderDefaultActions({ + params, + rowModesModel, + handleCancelClick, + handleSaveClick, + handleEditClick, + handleDeleteClick + }) + } + rowModesModel={rowModesModel} + handleRowModesModelChange={handleRowModesModelChange} + handleAddClick={handleAddClick} + handleStateChange={handleStateChange} + initialState={initialState} + gridApiRef={gridApiRef} + views={availableViews} + currentView={currentView.name || DEFAULT_STATE} + onChangeView={handleChangeView} + onCreateView={handleCreateView} + onDeleteView={handleDeleteView} + loading={isTableLoading} + /> + ) +} diff --git a/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/index.ts b/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/index.ts new file mode 100644 index 0000000..17449cc --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/MeetingType/index.ts @@ -0,0 +1 @@ +export * from './MeetingType' diff --git a/typescript/apps/admin-panel-web-spa/src/feature/index.ts b/typescript/apps/admin-panel-web-spa/src/feature/index.ts new file mode 100644 index 0000000..77b4507 --- /dev/null +++ b/typescript/apps/admin-panel-web-spa/src/feature/index.ts @@ -0,0 +1,3 @@ +export * from './GeoList' +export * from './MeetingType' +export * from './GeoConfig' diff --git a/typescript/apps/admin-panel-web-spa/src/routes/geo/$geoId/edit.tsx b/typescript/apps/admin-panel-web-spa/src/routes/geo/$geoId/edit.tsx index a06da69..c0ffcd9 100644 --- a/typescript/apps/admin-panel-web-spa/src/routes/geo/$geoId/edit.tsx +++ b/typescript/apps/admin-panel-web-spa/src/routes/geo/$geoId/edit.tsx @@ -1,6 +1,7 @@ import React from 'react' import { createFileRoute } from '@tanstack/react-router' +import { GeoEditPage } from '../../../feature' export const Route = createFileRoute('/geo/$geoId/edit')({ - component: () =>
Geo Edit
+ component: () => }) diff --git a/typescript/apps/admin-panel-web-spa/src/routes/geo/create.tsx b/typescript/apps/admin-panel-web-spa/src/routes/geo/create.tsx index 6ba8ba6..a2f241e 100644 --- a/typescript/apps/admin-panel-web-spa/src/routes/geo/create.tsx +++ b/typescript/apps/admin-panel-web-spa/src/routes/geo/create.tsx @@ -1,6 +1,7 @@ import React from 'react' import { createFileRoute } from '@tanstack/react-router' +import { GeoEditPage } from '../../feature' export const Route = createFileRoute('/geo/create')({ - component: () =>
Geo Create
+ component: () => }) diff --git a/typescript/apps/admin-panel-web-spa/src/routes/geo/index.tsx b/typescript/apps/admin-panel-web-spa/src/routes/geo/index.tsx index 6e5e55f..e5355a7 100644 --- a/typescript/apps/admin-panel-web-spa/src/routes/geo/index.tsx +++ b/typescript/apps/admin-panel-web-spa/src/routes/geo/index.tsx @@ -1,6 +1,7 @@ import React from 'react' import { createFileRoute } from '@tanstack/react-router' +import { GeoList } from '../../feature' export const Route = createFileRoute('/geo/')({ - component: () =>
Geo List
+ component: () => }) diff --git a/typescript/apps/admin-panel-web-spa/src/routes/index.tsx b/typescript/apps/admin-panel-web-spa/src/routes/index.tsx index 2a0e6ac..00edb82 100644 --- a/typescript/apps/admin-panel-web-spa/src/routes/index.tsx +++ b/typescript/apps/admin-panel-web-spa/src/routes/index.tsx @@ -1,6 +1,7 @@ import React from 'react' import { createFileRoute } from '@tanstack/react-router' +import { GeoList } from '../feature' export const Route = createFileRoute('/')({ - component: () =>
ROOT
+ component: () => }) diff --git a/typescript/apps/admin-panel-web-spa/src/routes/meeting-type.tsx b/typescript/apps/admin-panel-web-spa/src/routes/meeting-type.tsx index a53182d..73062ad 100644 --- a/typescript/apps/admin-panel-web-spa/src/routes/meeting-type.tsx +++ b/typescript/apps/admin-panel-web-spa/src/routes/meeting-type.tsx @@ -1,6 +1,7 @@ import React from 'react' import { createFileRoute } from '@tanstack/react-router' +import { MeetingTypeList } from '../feature' export const Route = createFileRoute('/meeting-type')({ - component: () =>
Meeting Type
+ component: () => }) diff --git a/typescript/apps/admin-panel-web-spa/src/testUtils/setupTests.ts b/typescript/apps/admin-panel-web-spa/src/testUtils/setupTests.ts index 0d8e620..d1c2c1b 100644 --- a/typescript/apps/admin-panel-web-spa/src/testUtils/setupTests.ts +++ b/typescript/apps/admin-panel-web-spa/src/testUtils/setupTests.ts @@ -2,6 +2,9 @@ import '@testing-library/jest-dom' import fetchMock from 'jest-fetch-mock' import { mswServer } from './server' +// TODO (#2029): Remove this after flaky tests fix +jest.setTimeout(30000) + beforeEach(() => { fetchMock.resetMocks() }) diff --git a/typescript/apps/admin-panel-web-spa/src/testUtils/setupUnitTests.ts b/typescript/apps/admin-panel-web-spa/src/testUtils/setupUnitTests.ts index f883466..fc3a610 100644 --- a/typescript/apps/admin-panel-web-spa/src/testUtils/setupUnitTests.ts +++ b/typescript/apps/admin-panel-web-spa/src/testUtils/setupUnitTests.ts @@ -1,6 +1,9 @@ import '@testing-library/jest-dom' import fetchMock from 'jest-fetch-mock' +// TODO (#2029): Remove this after flaky tests fix +jest.setTimeout(30000) + beforeEach(() => { fetchMock.resetMocks() }) diff --git a/typescript/apps/admin-panel-web-spa/src/types/geo.ts b/typescript/apps/admin-panel-web-spa/src/types/geo.ts index 8d268f8..146a6fa 100644 --- a/typescript/apps/admin-panel-web-spa/src/types/geo.ts +++ b/typescript/apps/admin-panel-web-spa/src/types/geo.ts @@ -1,4 +1,4 @@ -import { LinkField } from './common' +import { MeetingType } from './meetingType' export type Geo = { id: string @@ -8,15 +8,16 @@ export type Geo = { createdBy?: string name: string scheduleUrl: string - geoType: string + meetingType?: MeetingType timezone?: string captureScheduleFlag?: boolean captureStreamFlag?: boolean scheduleFormat?: string streamType?: string jurisdiction?: boolean - parent?: LinkField + parent?: Geo geoParentId?: number + meetingTypeId?: number channelUrl?: string statusSchedule?: string detectStartMethod: string @@ -32,11 +33,3 @@ export type Geo = { scheduleRefreshFrequency?: number streamTypeId?: number } -export type GeoProperties = { - geoTypes: string[] - detectMethods: string[] - statuses: string[] - timezones: string[] - scheduleFormats: string[] - streamTypes: string[] -} diff --git a/typescript/apps/admin-panel-web-spa/src/types/meetingType.ts b/typescript/apps/admin-panel-web-spa/src/types/meetingType.ts index 50e8622..f869758 100644 --- a/typescript/apps/admin-panel-web-spa/src/types/meetingType.ts +++ b/typescript/apps/admin-panel-web-spa/src/types/meetingType.ts @@ -1,11 +1,6 @@ export type MeetingType = { id: string - modifiedDate: string - createdDate: string - createdBy: string - title: string - sortkey: string - geo: string - geoParent: string - geoParent2: string + name: string + createdAt: string + updatedAt: string } diff --git a/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.spec.tsx b/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.spec.tsx index 2a301f8..528c42a 100644 --- a/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.spec.tsx +++ b/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.spec.tsx @@ -4,27 +4,21 @@ import { FieldValues, useForm } from 'react-hook-form' import { GeoConfigForm, GeoConfigFormProps } from './GeoConfigForm' import { Geo } from '../../types' -const mockOnSubmit = jest.fn() - const FormWithControl = () => { const { control } = useForm({ defaultValues: {} }) const defaultProps: GeoConfigFormProps = { - geoTypes: ['Type1', 'Type2'], isUpdateLoading: false, handleSubmit: jest.fn(), errors: {}, - onSubmit: mockOnSubmit, control, onParentSearch: jest.fn(), parentSearchLoading: false, parentOptions: [], - streamTypeOptions: [], - detectMethods: [], - statuses: [], - timezones: [], - scheduleFormats: [] + onMeetingTypeSearch: jest.fn(), + meetingTypeSearchLoading: false, + meetingTypeOptions: [] } return diff --git a/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.tsx b/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.tsx index b615d5f..7d631b6 100644 --- a/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.tsx +++ b/typescript/apps/admin-panel-web-spa/src/ui/GeoConfigForm/GeoConfigForm.tsx @@ -1,63 +1,42 @@ -import React, { FC } from 'react' -import { Controller, Control, FieldErrors, UseFormHandleSubmit, FieldValues } from 'react-hook-form' -import { - TextField, - Typography, - FormControl, - InputLabel, - Select, - MenuItem, - FormControlLabel, - Checkbox, - Button, - Grid -} from '@dbbs/mui-components' -import { CustomAutocomplete } from '../CustomAutocomplete' +import React, { FormEventHandler, FC } from 'react' +import { Controller, Control, FieldErrors, FieldValues } from 'react-hook-form' +import { TextField, Typography, FormControl, FormControlLabel, Checkbox, Button, Grid } from '@dbbs/mui-components' +import { CustomAutocomplete, Option } from '../CustomAutocomplete' import { DEFAULT_CONFIG_PAGE_GRID_SIZE } from '../../utils' import { GEO_CONFIG_FORM_TEST_IDS } from './testIds' -import { Geo, LinkField } from '../../types' export interface GeoConfigFormProps { - geoTypes: string[] isUpdateLoading: boolean - control?: Control - handleSubmit: UseFormHandleSubmit + control?: Control + handleSubmit: FormEventHandler errors: FieldErrors - onSubmit: (data: Geo) => void - onParentSearch: (searchTerm: string) => void parentSearchLoading: boolean - parentOptions: LinkField[] - streamTypeOptions: string[] - detectMethods: string[] - statuses: string[] - timezones: string[] - scheduleFormats: string[] + parentOptions: Option[] + onMeetingTypeSearch: (searchTerm: string) => void + meetingTypeSearchLoading: boolean + meetingTypeOptions: Option[] } export const GeoConfigForm: FC = ({ - geoTypes, isUpdateLoading, control, handleSubmit, errors, - onSubmit, onParentSearch, parentSearchLoading, parentOptions, - detectMethods, - statuses, - timezones, - scheduleFormats, - streamTypeOptions + onMeetingTypeSearch, + meetingTypeSearchLoading, + meetingTypeOptions }) => ( -
+ - + Geo Configuration - + = ({ /> - - + ( - - Geo Type - - - )} + label="Meeting Type" + options={meetingTypeOptions} + data-testid={GEO_CONFIG_FORM_TEST_IDS.GEO_TYPE} + onSearch={onMeetingTypeSearch} + loading={meetingTypeSearchLoading} + idKey="id" + labelKey="name" /> - + ( - - Time Zone - + + )} /> - + = ({ /> - + = ({ /> - + = ({ /> - + = ({ /> - + = ({ /> - + = ({ /> - + = ({ /> - + = ({ /> - + = ({ /> - - ( - - Stream Type - - - )} - /> - - - + - + = ({ /> - + ( - - Schedule Status - + + )} /> - + ( - - Stream Status - + + )} /> - + ( - - Detect Start Method - + + )} /> - + ( - - Detect End Method - + + )} /> - + = ({ /> - + = ({ /> - - ( - - - - )} - /> - - - + ( - Schedule Format - + )} /> - + + + +
-
- - -
- - - + + + Time Zone + + + +
renders correctly 1`] = ` @@ -216,7 +202,7 @@ exports[` renders correctly 1`] = ` renders correctly 1`] = `
-
- -
- - - - -
-
-
-
renders correctly 1`] = `
renders correctly 1`] = `
-
- - -
- - - + + + Schedule Status + + + +
-
- - -
- - - + + + Stream Status + + + +
-
- - -
- - - + + + Detect Start Method + + + +
-
- - -
- - - + + + Detect End Method + + + +
renders correctly 1`] = `
renders correctly 1`] = `
renders correctly 1`] = ` for=":re:" id=":re:-label" > - Single player URL + Schedule Format
renders correctly 1`] = ` aria-invalid="false" class="MuiInputBase-input MuiOutlinedInput-input css-1t8l2tu-MuiInputBase-input-MuiOutlinedInput-input" id=":re:" - name="singlePlayerUrl" + name="scheduleFormat" type="text" value="" /> @@ -1121,7 +955,7 @@ exports[` renders correctly 1`] = ` class="css-yjsfm1" > - Single player URL + Schedule Format @@ -1130,73 +964,7 @@ exports[` renders correctly 1`] = `
-
- -
- - - - -
-
-
-