diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7eb3709 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,114 @@ +# Flutter/Dart related +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +*.lock.json +.metadata + +# Android related +android/.gradle/ +android/app/build/ +android/gradle/ +android/.idea/ +android/local.properties +android/key.properties +android/app/release/ +android/app/debug/ +android/captures/ +android/*.iml +*.jks +*.keystore +*.p12 +google-services.json + +# iOS related +ios/Pods/ +ios/.symlinks/ +ios/Flutter/Flutter.framework +ios/Flutter/Flutter.podspec +ios/.generated/ +ios/Runner.xcworkspace/ +ios/Runner.xcodeproj/xcuserdata/ +ios/Runner.xcodeproj/project.xcworkspace/xcuserdata/ +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 +*.xcuserstate +xcuserdata/ +DerivedData/ +*.mobileprovision +*.cer +*.p12 +GoogleService-Info.plist + +# IDE related +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store +Thumbs.db +desktop.ini + +# Git +.git/ +.gitignore +.gitattributes +.github/ + +# CI/CD +.gitlab-ci.yml +.circleci/ +.travis.yml +azure-pipelines.yml + +# Testing and coverage +coverage/ +test/ +integration_test/ +.test_coverage/ +*.lcov +*.info + +# Documentation +README.md +README*.md +LICENSE +CHANGELOG.md +docs/ +CONTRIBUTING.md + +# Docker files +Dockerfile +Dockerfile.* +.dockerignore +docker-compose.yml +docker-compose.*.yml + +# Local configurations +.env +.env.* +.env.local +.env.*.local +*.log +*.log.* + +# Temporary files +*.tmp +*.temp +*.bak +*.swp +*~ + +# Firebase +firebase.json +.firebaserc +firestore.rules +firestore.indexes.json +storage.rules diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..9de6c81 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,225 @@ +name: Flutter CI/CD Pipeline + +on: + pull_request: + branches: [main, develop] + workflow_dispatch: + +env: + FLUTTER_VERSION: '3.38.4' + ANDROID_SDK_VERSION: '35' + +jobs: + # ========================================== + # Job 1: Code Quality & Analysis + # ========================================== + analyze: + name: Code Analysis + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: 'stable' + cache: true + + - name: Create fake firebase_options.dart + run: | + mkdir -p lib + cat < lib/firebase_options.dart + // ignore_for_file: type=lint + import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; + class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) throw UnsupportedError('Web not configured'); + switch (defaultTargetPlatform) { + case TargetPlatform.android: return android; + case TargetPlatform.iOS: return ios; + default: throw UnsupportedError('Platform not configured'); + } + } + static const FirebaseOptions android = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake'); + static const FirebaseOptions ios = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake', iosBundleId: 'fake'); + } + EOF + + - name: Get dependencies + run: flutter pub get + + - name: Run build_runner + run: dart run build_runner build --delete-conflicting-outputs + + - name: Format generated code + run: dart format . + + - name: Verify formatting + run: dart format --set-exit-if-changed lib/ test/ + continue-on-error: false + + - name: Analyze code + run: flutter analyze --fatal-infos + + - name: Run tests + run: flutter test --coverage || echo "โš ๏ธ No tests found" + + - name: Upload coverage to artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: code-coverage + path: coverage/lcov.info + if-no-files-found: ignore + retention-days: 7 + + # ========================================== + # Job 2: Android Build (Docker) + # ========================================== + build-android: + name: Build Android APK + runs-on: ubuntu-latest + needs: analyze + timeout-minutes: 60 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Free disk space + uses: endersonmenezes/free-disk-space@v3 + with: + remove_android: true + remove_dotnet: true + remove_haskell: true + remove_tool_cache: true + remove_swap: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Create mock configuration files + run: | + mkdir -p lib + # firebase_options.dart + echo "import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) throw UnsupportedError('Web not configured'); switch (defaultTargetPlatform) { case TargetPlatform.android: return android; case TargetPlatform.iOS: return ios; default: throw UnsupportedError('Platform not configured'); } } static const FirebaseOptions android = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake'); static const FirebaseOptions ios = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake', iosBundleId: 'fake'); }" > lib/firebase_options.dart + # google-services.json + echo '{"project_info":{"project_number":"123","project_id":"fake","storage_bucket":"fake"},"client":[{"client_info":{"mobilesdk_app_id":"1:123:android:123","android_client_info":{"package_name":"com.flutter.starter.template.flutter_starter_template"}},"api_key":[{"current_key":"fake"}]}]}' > android/app/google-services.json + + - name: Build Docker image + id: build + uses: docker/build-push-action@v5 + with: + context: . + push: false + load: true + tags: flutter-build:latest + build-args: | + BUILD_DATE=${{ github.event.repository.updated_at }} + VCS_REF=${{ github.sha }} + FLUTTER_VERSION=${{ env.FLUTTER_VERSION }} + ANDROID_SDK_VERSION=${{ env.ANDROID_SDK_VERSION }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Extract APK + run: | + docker create --name temp flutter-build:latest + mkdir -p apk-output + docker cp temp:/artifacts/. apk-output/ + docker rm temp + ls -lh apk-output/ + + - name: Upload Android APK artifacts + uses: actions/upload-artifact@v4 + with: + name: android-apk-${{ github.sha }} + path: apk-output/*.apk + retention-days: 30 + compression-level: 0 + + - name: Generate Summary + run: | + echo "## ๐Ÿค– Android APK Build Results" >> $GITHUB_STEP_SUMMARY + echo "| Architecture | File | Size |" >> $GITHUB_STEP_SUMMARY + echo "|--------------|----------|------|" >> $GITHUB_STEP_SUMMARY + for apk in apk-output/*.apk; do + name=$(basename "$apk") + size=$(du -h "$apk" | cut -f1) + arch="Universal" + if [[ "$name" == *"arm64"* ]]; then arch="ARM64"; fi + if [[ "$name" == *"armeabi"* ]]; then arch="ARMv7"; fi + if [[ "$name" == *"x86_64"* ]]; then arch="x86_64"; fi + echo "| $arch | \`$name\` | $size |" >> $GITHUB_STEP_SUMMARY + done + + # ========================================== + # Job 3: iOS Build (macOS runner) + # ========================================== + build-ios: + name: Build iOS IPA + runs-on: macos-latest + needs: analyze + timeout-minutes: 45 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.FLUTTER_VERSION }} + channel: 'stable' + cache: true + + - name: Create mock configuration files + run: | + mkdir -p lib ios/Runner + # Create firebase_options.dart + echo "import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; class DefaultFirebaseOptions { static FirebaseOptions get currentPlatform { if (kIsWeb) throw UnsupportedError('Web not configured'); switch (defaultTargetPlatform) { case TargetPlatform.android: return android; case TargetPlatform.iOS: return ios; default: throw UnsupportedError('Platform not configured'); } } static const FirebaseOptions android = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake'); static const FirebaseOptions ios = FirebaseOptions(apiKey: 'fake', appId: 'fake', messagingSenderId: 'fake', projectId: 'fake', storageBucket: 'fake', iosBundleId: 'fake'); }" > lib/firebase_options.dart + # Create GoogleService-Info.plist + echo 'API_KEYfakeGCM_SENDER_ID123PLIST_VERSION1BUNDLE_IDcom.flutter.starter.template.flutterStarterTemplatePROJECT_IDfakeSTORAGE_BUCKETfakeIS_ADS_ENABLEDIS_ANALYTICS_ENABLEDIS_APPINVITE_ENABLEDIS_GCM_ENABLEDIS_SIGNIN_ENABLEDGOOGLE_APP_ID1:123:ios:123' > ios/Runner/GoogleService-Info.plist + + - name: Get dependencies + run: flutter pub get + + - name: Install Pods + run: | + cd ios + rm -rf Pods Podfile.lock + pod install --repo-update + + - name: Build iOS app + run: | + flutter build ios --release --no-codesign --build-number=${{ github.run_number }} + + - name: Create IPA + run: | + mkdir -p Payload + cp -R build/ios/iphoneos/Runner.app Payload/ + IPA_NAME="flutter-starter-ios-${{ github.run_number }}-unsigned.ipa" + zip -r "$IPA_NAME" Payload + rm -rf Payload + + - name: Upload iOS IPA artifact + uses: actions/upload-artifact@v4 + with: + name: ios-ipa-${{ github.sha }} + path: "*.ipa" + retention-days: 30 + compression-level: 0 + + - name: Generate Summary + run: | + echo "## ๐ŸŽ iOS IPA Build Results" >> $GITHUB_STEP_SUMMARY + echo "| File | Size |" >> $GITHUB_STEP_SUMMARY + echo "|------|------|" >> $GITHUB_STEP_SUMMARY + for ipa in *.ipa; do + size=$(du -h "$ipa" | cut -f1) + echo "| \`$ipa\` | $size |" >> $GITHUB_STEP_SUMMARY + done + echo "โš ๏ธ Unsigned IPA for testing only." >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index c6ca0e1..c539165 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,22 @@ linked_*.ds unlinked.ds unlinked_spec.ds +# Build Runner generated +*.g.dart +*.mocks.dart + +# Firebase related +firebase_options.dart + +# Docker related +*.img +*.tar +*.tar.gz + +# APK related +*.apk +apk-output/ + # Symbolication related app.*.symbols @@ -127,4 +143,4 @@ ios/Runner/GoogleService-Info.plist android/app/google-services.json # Generated localization -lib/core/localization/generated/ \ No newline at end of file +lib/core/localization/generated/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 59554c0..859871f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ---- +## [0.2.0] - 2025-12-20 + +### Added + +- Implemented base Clean Architecture structure (`core`, `features`, `uikit`) with `data`, `domain`, and `presentation` layers. +- Added project dependencies from `pubspec.yaml`. +- Integrated `GetIt` for dependency injection. +- Added `DebugScreen` widget for testing and debugging purposes. +- Added application router with `GoRouter` and defined route paths for navigation. +- Integrated `logger` package with `AppLogger` abstraction, DI setup, and global error handling (Flutter & platform-level). +- Integrated `Firebase Core` for authentication and analytics features. +- Integrated `Firebase Crashlytics` for global error handling. +- Added `AppFailure` base class and `UnknownFailure` implementation for unified domain-level error handling. +- Added sealed `Result` abstraction with `Success` and `Failure` implementations, plus `AsyncResult` type alias for standardizing async operation handling. +- Added DI-independent fallback error handler inside `runZonedGuarded` to ensure reliable error reporting even if DI or Firebase are not ready. +- Added unified analytics architecture with `Firebase Analytics` integration, debug logging, and automatic screen view tracking via `AnalyticsRouteObserver`. +- Added `AppLoggerMixin` for convenient logger access in Bloc/Cubit. +- Added CI/CD pipeline with GitHub Actions: + - Multi-stage Dockerfile with Flutter 3.38.4 and Android SDK 35. + - Support for split APK by ABI (armeabi-v7a, arm64-v8a, x86_64). + - `.dockerignore` for optimized build context. + - Workflow jobs: code analysis, Android build (Docker + DockerHub), iOS build (macOS runner). + - Artifact uploads and build summaries. +- Added `NetworkService` for monitoring network availability with `connectivity_plus` integration and unit tests. + +### Changed + +- Updated `analysis_options.yaml` (including enabling `public_member_api_docs`). +- Refactored app startup: + - Extracted main application widget to `app.dart`. + - Added `runner.dart` for app initialization and error handling. + - Simplified `main.dart`. +- Streamlined logger registration in dependency injection setup. +- Updated Dart SDK constraint to `>=3.10.0 <4.0.0`. + +### Fixed + +- Moved `WidgetsFlutterBinding.ensureInitialized()` inside `runZonedGuarded` in `runner.dart` to ensure proper app initialization and error handling. +- Fixed code generation failure by updating `retrofit_generator` for compatibility with newer `analyzer` versions. ## [0.1.0] - 2025-10-29 + ### Added + - Initial Flutter project created via `flutter create`. - Added core documentation files and base configuration. - -*This changelog was initialized with the project setup (initial commit). Future changes will be tracked starting from version 0.1.0.* \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 92a294a..7f44b6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,46 +21,53 @@ Please take a few minutes to read through these guidelines before submitting you 1. **Fork** the repository. 2. **Clone** your fork locally: -```bash - git clone https://github.com/CowboyGH/flutter-starter-template.git - cd -``` + + ```bash + git clone https://github.com/CowboyGH/flutter-starter-template.git + cd + ``` 3. Install dependencies: -```bash - flutter pub get -``` + + ```bash + flutter pub get + ``` 4. Create a new branch for your feature or fix: -```bash - git checkout -b feature/my-new-feature -``` + + ```bash + git checkout -b feature/my-new-feature + ``` --- ## ๐Ÿงฐ Development Workflow 1. Make sure your Flutter SDK and packages are up to date: -```bash - flutter upgrade - flutter pub upgrade -``` + + ```bash + flutter upgrade + flutter pub upgrade + ``` 2. Run the project: -```bash - flutter run -``` + + ```bash + flutter run + ``` 3. Before committing, check formatting and analyze code: -```bash - flutter format . - flutter analyze -``` + + ```bash + flutter format . + flutter analyze + ``` 4. Run unit tests (if any): -```bash - flutter test -``` + + ```bash + flutter test + ``` --- @@ -77,12 +84,14 @@ Please take a few minutes to read through these guidelines before submitting you ## ๐Ÿ“ Commit Guidelines Follow [Conventional Commits](https://www.conventionalcommits.org/) format: -``` + +```text (optional scope): ``` **Example:** -``` + +```text feat(auth): add user login with token refresh fix(router): correct initial route for onboarding docs(readme): update setup instructions @@ -111,6 +120,7 @@ I use a simple branching model: - `docs/*` โ€” documentation updates **Example workflow:** + ```bash git checkout -b feature/add-user-profile # make changes @@ -142,4 +152,4 @@ If you have questions or run into issues: --- -Thank you for contributing and helping make this project better! ๐Ÿš€ \ No newline at end of file +Thank you for contributing and helping make this project better! ๐Ÿš€ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4b784a6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,180 @@ +# ========================================== +# Global ARGs +# ========================================== +ARG TARGETPLATFORM=linux/amd64 +ARG FLUTTER_VERSION=3.38.4 +ARG ANDROID_SDK_VERSION=35 +ARG ANDROID_BUILD_TOOLS_VERSION=35.0.0 +ARG CMDLINE_TOOLS_VERSION=13114758 + + +# ========================================== +# Stage 1: Builder - Android APK build +# ========================================== +FROM --platform=$TARGETPLATFORM ubuntu:22.04 AS builder + + +# Use global ARGs +ARG FLUTTER_VERSION +ARG ANDROID_SDK_VERSION +ARG ANDROID_BUILD_TOOLS_VERSION +ARG CMDLINE_TOOLS_VERSION + + +# Disable interactive prompts +ENV DEBIAN_FRONTEND=noninteractive + + +# Install required dependencies +RUN apt-get update && apt-get install -y \ + curl \ + git \ + unzip \ + xz-utils \ + zip \ + libglu1-mesa \ + openjdk-17-jdk \ + wget \ + ca-certificates \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + + +# Detect actual Java path and create symlink if needed +RUN REAL_JAVA=$(dirname $(dirname $(readlink -f $(which javac)))) && \ + echo "Real Java path: ${REAL_JAVA}" && \ + if [ "${REAL_JAVA}" != "/usr/lib/jvm/java-17-openjdk-amd64" ]; then \ + mkdir -p /usr/lib/jvm && \ + ln -sf ${REAL_JAVA} /usr/lib/jvm/java-17-openjdk-amd64; \ + fi + + +# Set JAVA_HOME via ENV (works for all subsequent RUN commands) +ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +ENV PATH=$JAVA_HOME/bin:$PATH + + +# Verify that Java is working +RUN java -version && javac -version && \ + echo "JAVA_HOME is set to: ${JAVA_HOME}" && \ + ls -la ${JAVA_HOME}/bin/java + + +# Configure Android SDK +ENV ANDROID_SDK_ROOT=/opt/android-sdk +ENV ANDROID_HOME=$ANDROID_SDK_ROOT +ENV PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/build-tools/${ANDROID_BUILD_TOOLS_VERSION} + + +# Create directory for Android SDK +RUN mkdir -p $ANDROID_SDK_ROOT/cmdline-tools + + +# Download and install Android Command Line Tools +RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip -O /tmp/cmdline-tools.zip && \ + unzip -q /tmp/cmdline-tools.zip -d $ANDROID_SDK_ROOT/cmdline-tools && \ + mv $ANDROID_SDK_ROOT/cmdline-tools/cmdline-tools $ANDROID_SDK_ROOT/cmdline-tools/latest && \ + rm /tmp/cmdline-tools.zip + + +# Install required Android SDK components (JAVA_HOME already set via ENV) +RUN echo "Current JAVA_HOME: ${JAVA_HOME}" && \ + yes | sdkmanager --licenses && \ + sdkmanager --install \ + "platform-tools" \ + "platforms;android-${ANDROID_SDK_VERSION}" \ + "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ + "cmdline-tools;latest" + + +# Download Flutter SDK (release archive instead of git clone for speed) +RUN wget -q https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_${FLUTTER_VERSION}-stable.tar.xz -O /tmp/flutter.tar.xz && \ + tar xf /tmp/flutter.tar.xz -C /opt && \ + rm /tmp/flutter.tar.xz + + +# Configure environment variables for Flutter +ENV FLUTTER_ROOT=/opt/flutter +ENV PATH="${FLUTTER_ROOT}/bin:${FLUTTER_ROOT}/bin/cache/dart-sdk/bin:$PATH" +ENV PUB_CACHE=/root/.pub-cache + + +# Fix Git safe.directory warning +RUN git config --global --add safe.directory /opt/flutter && \ + git config --global --add safe.directory "${FLUTTER_ROOT}" + + +# Initial Flutter setup +RUN flutter doctor -v && \ + flutter config --no-analytics && \ + flutter config --android-sdk $ANDROID_SDK_ROOT && \ + flutter precache --android --no-ios --no-web + + +# Create directories for caching +RUN mkdir -p $PUB_CACHE && \ + mkdir -p /root/.gradle + + +# Set working directory +WORKDIR /app + + +# Copy dependency files to leverage layer caching +COPY pubspec.yaml pubspec.lock ./ + + +# Install dependencies +RUN flutter pub get + + +# Copy entire project +COPY . . + + +# Build Android APK in release mode +RUN flutter build apk --release --split-per-abi --no-tree-shake-icons + + +# Verify that the build succeeded +RUN test -f /app/build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk || \ + test -f /app/build/app/outputs/flutter-apk/app-arm64-v8a-release.apk || \ + (echo "ERROR: APK files not found!" && exit 1) + + +# ========================================== +# Stage 2: Runtime - Artifact storage +# ========================================== +FROM alpine:latest AS runtime + + +# Set image metadata +ARG FLUTTER_VERSION +ARG BUILD_DATE +ARG VCS_REF + + +LABEL maintainer="cowboycloud@icloud.com" \ + description="Flutter Android APK Build Artifacts" \ + version="1.0.0" \ + flutter.version="${FLUTTER_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.revision="${VCS_REF}" \ + org.opencontainers.image.title="Flutter Starter Template Android Build" \ + org.opencontainers.image.description="Production-ready Android APK artifacts" + + +# Create directory for APKs +RUN mkdir -p /artifacts + + +# Copy built APKs from builder stage +COPY --from=builder /app/build/app/outputs/flutter-apk/*.apk /artifacts/ + + +# Set working directory +WORKDIR /artifacts + + +# Default command - list files with sizes +CMD ["ls", "-lh"] diff --git a/README.md b/README.md index 712e067..505b275 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,256 @@ # ๐Ÿงฉ Flutter Starter Template -A scalable **Flutter Starter Template** for building production-ready business applications. -Includes preconfigured architecture, dependency injection, routing, and modular feature structure. +A pragmatic **Flutter Starter Template** for personal and academic projects, built with **Clean Architecture**, **Firebase integration**, and **best practices** out of the box. + +[![Flutter Version](https://img.shields.io/badge/Flutter-3.10.0+-blue.svg)](https://flutter.dev/) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +--- + +## ๐Ÿ“š Table of Contents + +- [๐Ÿš€ Highlights](#-highlights) +- [๐Ÿ“ฆ Tech Stack](#-tech-stack) +- [๐Ÿ—๏ธ Architecture](#๏ธ-architecture) +- [๐Ÿ“‚ Project Structure](#-project-structure) +- [๐Ÿ› ๏ธ Getting Started](#๏ธ-getting-started) +- [๐Ÿงช Testing](#-testing) +- [๐Ÿ‹ Docker Build](#-docker-build) +- [๐Ÿง  Why This Template?](#-why-this-template) +- [๐Ÿ“ Documentation](#-documentation) +- [๐Ÿค Contributing](#-contributing) +- [๐Ÿ“œ License](#-license) +- [โœจ Author](#-author) + +--- + +## ๐Ÿš€ Highlights + +- ๐Ÿ›๏ธ **Clean Architecture** (Data โ†’ Domain โ†’ Presentation) +- ๐Ÿ’‰ **Dependency Injection** (GetIt + Provider) +- ๐Ÿ”ฅ **Firebase** integration (Core, Crashlytics, Analytics) +- โš ๏ธ **Error Handling** (Result pattern + Failure hierarchy) +- ๐Ÿ“ **Logging** with AppLogger abstraction and Mixin for Bloc/Cubit +- ๐Ÿงญ **Navigation** with GoRouter and automatic screen tracking +- ๐Ÿ“ฆ **Modular Structure** for scalable feature development +- ๐ŸŽจ **Shared UIKit** (buttons, images, themes) + +--- + +## ๐Ÿ“ฆ Tech Stack + +| Category | Technology | +| ---------- | ----------- | +| **Framework** | Flutter 3.10.0+ | +| **State Management** | flutter_bloc | +| **Dependency Injection** | get_it, provider | +| **Backend** | Firebase (Core, Auth, Crashlytics, Analytics, Firestore, Storage) | +| **Navigation** | go_router | +| **Connectivity Tracking** | connectivity_plus | +| **HTTP Client** | dio, retrofit | +| **UI Components** | flutter_svg, cached_network_image | +| **Code Generation** | freezed, json_serializable, build_runner | +| **Logging** | logger | +| **Localization** | intl, flutter_localizations | +| **Testing** | mockito, bloc_test, flutter_test | +| **Local Storage** | shared_preferences | + +--- + +## ๐Ÿ—๏ธ Architecture + +This template follows **Clean Architecture** principles: + +```text +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Presentation Layer โ”‚ +โ”‚ (Pages, Widgets, BLoC/Cubit) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ uses +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Domain Layer โ”‚ +โ”‚ (Entities, UseCases, Repositories*) โ”‚ * interfaces only +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ฒโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ implements +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Data Layer โ”‚ +โ”‚ (DTOs, DataSources, Repositories Impl) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Dependency Rule + +`Presentation โ†’ Domain โ† Data` + +**Domain Layer** does NOT know about: + +- Flutter framework (`import 'package:flutter/...'` โŒ) +- Firebase, Dio, SharedPreferences +- JSON serialization (`@JsonSerializable` only in Data layer) + +--- + +## ๐Ÿ“‚ Project Structure + +```text +lib/ +โ”œโ”€โ”€ core/ # Shared infrastructure +โ”‚ โ”œโ”€โ”€ di/ # Dependency injection setup +โ”‚ โ”œโ”€โ”€ failures/ # Error handling (Failure) +โ”‚ โ”œโ”€โ”€ localization/ # Localization files +โ”‚ โ”œโ”€โ”€ result/ # Result-pattern +โ”‚ โ”œโ”€โ”€ router/ # Navigation (GoRouter, paths) +โ”‚ โ”œโ”€โ”€ services/ # Services (Network, etc.) +โ”‚ โ””โ”€โ”€ utils/ # Utilities (Logger, Analytics) +โ”œโ”€โ”€ features/ # Feature modules (Clean Architecture) +โ”‚ โ””โ”€โ”€ [feature_name]/ +โ”‚ โ”œโ”€โ”€ data/ # DTOs, DataSources, Repository Impl +โ”‚ โ”œโ”€โ”€ domain/ # Entities, UseCases, Repository Interfaces +โ”‚ โ””โ”€โ”€ presentation/ # Pages, Widgets, BLoC/Cubit +โ”œโ”€โ”€ uikit/ # Shared UI components +โ””โ”€โ”€ main.dart # App entry point +``` + +--- + +## ๐Ÿ› ๏ธ Getting Started + +### Prerequisites + +- Flutter SDK: `>=3.10.0` +- Dart SDK: `>=3.10.0` +- Firebase project (for Analytics, Crashlytics) + +### Installation + +1. **Clone or use this template:** + + ```bash + # Option 1: Clone + git clone https://github.com/CowboyGH/flutter_starter_template.git + + # Option 2: Use template (on GitHub: "Use this template" button) + ``` + +2. **Install dependencies:** + + ```bash + flutter pub get + ``` + +3. **Configure Firebase:** + + ```bash + # Install FlutterFire CLI + dart pub global activate flutterfire_cli + + # Configure Firebase for your project + flutterfire configure + ``` + +4. **Generate localization files:** + + ```bash + flutter gen-l10n + ``` + +5. **Run code generation:** + + ```bash + flutter pub run build_runner build --delete-conflicting-outputs + ``` + +6. **Run the app:** + + ```bash + flutter run + ``` + +--- + +## ๐Ÿงช Testing + +Run tests: + +```bash +# Unit tests +flutter test + +# Widget tests +flutter test test/features/*/presentation/ + +# Coverage (requires lcov) +flutter test --coverage +genhtml coverage/lcov.info -o coverage/html +``` + +--- + +## ๐Ÿ‹ Docker Build + +Build Android APK using Docker: + +```bash +docker build + --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') + --build-arg VCS_REF=$(git rev-parse --short HEAD) + --progress=plain + --no-cache + -t flutter-starter-android:latest . +``` + +Extract APK files: + +```bash +docker create --name temp-flutter flutter-starter-android:latest +docker cp temp-flutter:/artifacts/. ./apk-output/ +docker rm temp-flutter +``` + +--- + +## ๐Ÿง  Why This Template? + +This repository is primarily a **personal starter** used to move faster on personal and academic projects. + +### Goals + +- Provide a stable baseline with **Clean Architecture** and strict layer boundaries. +- Have a ready-to-use infrastructure for **errors**, **logging**, **navigation**, and **CI/CD**. +- Reduce recurring setup work so feature development starts from a consistent foundation. + +### Non-goals (at least for now) + +- Competing with popular community templates. +- Shipping a complete UI kit/design system out of the box. +- Covering every possible app scenario or advanced product requirements. + +If you find it useful โ€” great! If not, treat it as a reference implementation of the patterns listed above. + +--- + +## ๐Ÿ“ Documentation + +- [CHANGELOG](CHANGELOG.md) - Version history and release notes +- [CONTRIBUTING](CONTRIBUTING.md) - Development guidelines and coding standards +- [LICENSE](LICENSE) - MIT License + +--- + +## ๐Ÿค Contributing + +Issues and PRs are welcome, but the template is primarily maintained for personal use. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. --- ## ๐Ÿ“œ License -This project is released under the [MIT License](LICENSE). +This project is licensed under the [MIT License](LICENSE). --- ## โœจ Author -Developed by [Ryan Delaney](https://github.com/CowboyGH) \ No newline at end of file +Developed by [Ryan Delaney](https://github.com/CowboyGH) diff --git a/analysis_options.yaml b/analysis_options.yaml index 13ffaac..da296b7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -31,7 +31,6 @@ linter: rules: # --- Style & Formatting --- - always_declare_return_types - - always_put_control_body_on_new_line - annotate_overrides - avoid_print - avoid_redundant_argument_values @@ -42,6 +41,7 @@ linter: - prefer_const_declarations - prefer_relative_imports - prefer_single_quotes + - public_member_api_docs - use_super_parameters - unnecessary_parenthesis diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index cfc331e..09adac3 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,5 +1,9 @@ plugins { id("com.android.application") + // START: FlutterFire Configuration + id("com.google.gms.google-services") + id("com.google.firebase.crashlytics") + // 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") diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e10f231..ccaa432 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ + + 12.4.0) + - Firebase/CoreOnly (12.4.0): + - FirebaseCore (~> 12.4.0) + - Firebase/Crashlytics (12.4.0): + - Firebase/CoreOnly + - FirebaseCrashlytics (~> 12.4.0) + - Firebase/Firestore (12.4.0): + - Firebase/CoreOnly + - FirebaseFirestore (~> 12.4.0) + - Firebase/Storage (12.4.0): + - Firebase/CoreOnly + - FirebaseStorage (~> 12.4.0) + - firebase_analytics (12.0.4): + - firebase_core + - FirebaseAnalytics (= 12.4.0) + - Flutter + - firebase_auth (6.1.2): + - Firebase/Auth (= 12.4.0) + - firebase_core + - Flutter + - firebase_core (4.2.1): + - Firebase/CoreOnly (= 12.4.0) + - Flutter + - firebase_crashlytics (5.0.5): + - Firebase/Crashlytics (= 12.4.0) + - firebase_core + - Flutter + - firebase_storage (13.0.4): + - Firebase/Storage (= 12.4.0) + - firebase_core + - Flutter + - FirebaseAnalytics (12.4.0): + - FirebaseAnalytics/Default (= 12.4.0) + - FirebaseCore (~> 12.4.0) + - FirebaseInstallations (~> 12.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAnalytics/Default (12.4.0): + - FirebaseCore (~> 12.4.0) + - FirebaseInstallations (~> 12.4.0) + - GoogleAppMeasurement/Default (= 12.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAppCheckInterop (12.4.0) + - FirebaseAuth (12.4.0): + - FirebaseAppCheckInterop (~> 12.4.0) + - FirebaseAuthInterop (~> 12.4.0) + - FirebaseCore (~> 12.4.0) + - FirebaseCoreExtension (~> 12.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/Environment (~> 8.1) + - GTMSessionFetcher/Core (< 6.0, >= 3.4) + - RecaptchaInterop (~> 101.0) + - FirebaseAuthInterop (12.4.0) + - FirebaseCore (12.4.0): + - FirebaseCoreInternal (~> 12.4.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreExtension (12.4.0): + - FirebaseCore (~> 12.4.0) + - FirebaseCoreInternal (12.4.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseCrashlytics (12.4.0): + - FirebaseCore (~> 12.4.0) + - FirebaseInstallations (~> 12.4.0) + - FirebaseRemoteConfigInterop (~> 12.4.0) + - FirebaseSessions (~> 12.4.0) + - GoogleDataTransport (~> 10.1) + - GoogleUtilities/Environment (~> 8.1) + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - FirebaseFirestore (12.4.0): + - FirebaseCore (~> 12.4.0) + - FirebaseCoreExtension (~> 12.4.0) + - FirebaseFirestoreInternal (~> 12.4.0) + - FirebaseSharedSwift (~> 12.4.0) + - FirebaseFirestoreInternal (12.4.0): + - abseil/algorithm (~> 1.20240722.0) + - abseil/base (~> 1.20240722.0) + - abseil/container/flat_hash_map (~> 1.20240722.0) + - abseil/memory (~> 1.20240722.0) + - abseil/meta (~> 1.20240722.0) + - abseil/strings/strings (~> 1.20240722.0) + - abseil/time (~> 1.20240722.0) + - abseil/types (~> 1.20240722.0) + - FirebaseAppCheckInterop (~> 12.4.0) + - FirebaseCore (~> 12.4.0) + - "gRPC-C++ (~> 1.69.0)" + - gRPC-Core (~> 1.69.0) + - leveldb-library (~> 1.22) + - nanopb (~> 3.30910.0) + - FirebaseInstallations (12.4.0): + - FirebaseCore (~> 12.4.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - PromisesObjC (~> 2.4) + - FirebaseRemoteConfigInterop (12.4.0) + - FirebaseSessions (12.4.0): + - FirebaseCore (~> 12.4.0) + - FirebaseCoreExtension (~> 12.4.0) + - FirebaseInstallations (~> 12.4.0) + - GoogleDataTransport (~> 10.1) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - nanopb (~> 3.30910.0) + - PromisesSwift (~> 2.1) + - FirebaseSharedSwift (12.4.0) + - FirebaseStorage (12.4.0): + - FirebaseAppCheckInterop (~> 12.4.0) + - FirebaseAuthInterop (~> 12.4.0) + - FirebaseCore (~> 12.4.0) + - FirebaseCoreExtension (~> 12.4.0) + - GoogleUtilities/Environment (~> 8.1) + - GTMSessionFetcher/Core (< 6.0, >= 3.4) + - Flutter (1.0.0) + - GoogleAdsOnDeviceConversion (3.1.0): + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Core (12.4.0): + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Default (12.4.0): + - GoogleAdsOnDeviceConversion (~> 3.1.0) + - GoogleAppMeasurement/Core (= 12.4.0) + - GoogleAppMeasurement/IdentitySupport (= 12.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/IdentitySupport (12.4.0): + - GoogleAppMeasurement/Core (= 12.4.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleDataTransport (10.1.0): + - nanopb (~> 3.30910.0) + - PromisesObjC (~> 2.4) + - GoogleUtilities/AppDelegateSwizzler (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.1.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.1.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/Reachability (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - "gRPC-C++ (1.69.0)": + - "gRPC-C++/Implementation (= 1.69.0)" + - "gRPC-C++/Interface (= 1.69.0)" + - "gRPC-C++/Implementation (1.69.0)": + - abseil/algorithm/container (~> 1.20240722.0) + - abseil/base/base (~> 1.20240722.0) + - abseil/base/config (~> 1.20240722.0) + - abseil/base/core_headers (~> 1.20240722.0) + - abseil/base/log_severity (~> 1.20240722.0) + - abseil/base/no_destructor (~> 1.20240722.0) + - abseil/cleanup/cleanup (~> 1.20240722.0) + - abseil/container/flat_hash_map (~> 1.20240722.0) + - abseil/container/flat_hash_set (~> 1.20240722.0) + - abseil/container/inlined_vector (~> 1.20240722.0) + - abseil/flags/flag (~> 1.20240722.0) + - abseil/flags/marshalling (~> 1.20240722.0) + - abseil/functional/any_invocable (~> 1.20240722.0) + - abseil/functional/bind_front (~> 1.20240722.0) + - abseil/functional/function_ref (~> 1.20240722.0) + - abseil/hash/hash (~> 1.20240722.0) + - abseil/log/absl_check (~> 1.20240722.0) + - abseil/log/absl_log (~> 1.20240722.0) + - abseil/log/check (~> 1.20240722.0) + - abseil/log/globals (~> 1.20240722.0) + - abseil/log/log (~> 1.20240722.0) + - abseil/memory/memory (~> 1.20240722.0) + - abseil/meta/type_traits (~> 1.20240722.0) + - abseil/numeric/bits (~> 1.20240722.0) + - abseil/random/bit_gen_ref (~> 1.20240722.0) + - abseil/random/distributions (~> 1.20240722.0) + - abseil/random/random (~> 1.20240722.0) + - abseil/status/status (~> 1.20240722.0) + - abseil/status/statusor (~> 1.20240722.0) + - abseil/strings/cord (~> 1.20240722.0) + - abseil/strings/str_format (~> 1.20240722.0) + - abseil/strings/strings (~> 1.20240722.0) + - abseil/synchronization/synchronization (~> 1.20240722.0) + - abseil/time/time (~> 1.20240722.0) + - abseil/types/optional (~> 1.20240722.0) + - abseil/types/span (~> 1.20240722.0) + - abseil/types/variant (~> 1.20240722.0) + - abseil/utility/utility (~> 1.20240722.0) + - "gRPC-C++/Interface (= 1.69.0)" + - "gRPC-C++/Privacy (= 1.69.0)" + - gRPC-Core (= 1.69.0) + - "gRPC-C++/Interface (1.69.0)" + - "gRPC-C++/Privacy (1.69.0)" + - gRPC-Core (1.69.0): + - gRPC-Core/Implementation (= 1.69.0) + - gRPC-Core/Interface (= 1.69.0) + - gRPC-Core/Implementation (1.69.0): + - abseil/algorithm/container (~> 1.20240722.0) + - abseil/base/base (~> 1.20240722.0) + - abseil/base/config (~> 1.20240722.0) + - abseil/base/core_headers (~> 1.20240722.0) + - abseil/base/log_severity (~> 1.20240722.0) + - abseil/base/no_destructor (~> 1.20240722.0) + - abseil/cleanup/cleanup (~> 1.20240722.0) + - abseil/container/flat_hash_map (~> 1.20240722.0) + - abseil/container/flat_hash_set (~> 1.20240722.0) + - abseil/container/inlined_vector (~> 1.20240722.0) + - abseil/flags/flag (~> 1.20240722.0) + - abseil/flags/marshalling (~> 1.20240722.0) + - abseil/functional/any_invocable (~> 1.20240722.0) + - abseil/functional/bind_front (~> 1.20240722.0) + - abseil/functional/function_ref (~> 1.20240722.0) + - abseil/hash/hash (~> 1.20240722.0) + - abseil/log/check (~> 1.20240722.0) + - abseil/log/globals (~> 1.20240722.0) + - abseil/log/log (~> 1.20240722.0) + - abseil/memory/memory (~> 1.20240722.0) + - abseil/meta/type_traits (~> 1.20240722.0) + - abseil/numeric/bits (~> 1.20240722.0) + - abseil/random/bit_gen_ref (~> 1.20240722.0) + - abseil/random/distributions (~> 1.20240722.0) + - abseil/random/random (~> 1.20240722.0) + - abseil/status/status (~> 1.20240722.0) + - abseil/status/statusor (~> 1.20240722.0) + - abseil/strings/cord (~> 1.20240722.0) + - abseil/strings/str_format (~> 1.20240722.0) + - abseil/strings/strings (~> 1.20240722.0) + - abseil/synchronization/synchronization (~> 1.20240722.0) + - abseil/time/time (~> 1.20240722.0) + - abseil/types/optional (~> 1.20240722.0) + - abseil/types/span (~> 1.20240722.0) + - abseil/types/variant (~> 1.20240722.0) + - abseil/utility/utility (~> 1.20240722.0) + - BoringSSL-GRPC (= 0.0.37) + - gRPC-Core/Interface (= 1.69.0) + - gRPC-Core/Privacy (= 1.69.0) + - gRPC-Core/Interface (1.69.0) + - gRPC-Core/Privacy (1.69.0) + - GTMSessionFetcher/Core (5.0.0) + - leveldb-library (1.22.6) + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - PromisesObjC (2.4.0) + - PromisesSwift (2.4.0): + - PromisesObjC (= 2.4.0) + - RecaptchaInterop (101.0.0) + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - firebase_analytics (from `.symlinks/plugins/firebase_analytics/ios`) + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) + - firebase_storage (from `.symlinks/plugins/firebase_storage/ios`) + - Flutter (from `Flutter`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + +SPEC REPOS: + trunk: + - abseil + - BoringSSL-GRPC + - Firebase + - FirebaseAnalytics + - FirebaseAppCheckInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - FirebaseCrashlytics + - FirebaseFirestore + - FirebaseFirestoreInternal + - FirebaseInstallations + - FirebaseRemoteConfigInterop + - FirebaseSessions + - FirebaseSharedSwift + - FirebaseStorage + - GoogleAdsOnDeviceConversion + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleUtilities + - "gRPC-C++" + - gRPC-Core + - GTMSessionFetcher + - leveldb-library + - nanopb + - PromisesObjC + - PromisesSwift + - RecaptchaInterop + +EXTERNAL SOURCES: + cloud_firestore: + :path: ".symlinks/plugins/cloud_firestore/ios" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + firebase_analytics: + :path: ".symlinks/plugins/firebase_analytics/ios" + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + firebase_crashlytics: + :path: ".symlinks/plugins/firebase_crashlytics/ios" + firebase_storage: + :path: ".symlinks/plugins/firebase_storage/ios" + Flutter: + :path: Flutter + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + +SPEC CHECKSUMS: + abseil: a05cc83bf02079535e17169a73c5be5ba47f714b + BoringSSL-GRPC: dded2a44897e45f28f08ae87a55ee4bcd19bc508 + cloud_firestore: 7a6d8a533ec7418a7fe46b3a5dabf55661a5b298 + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + Firebase: f07b15ae5a6ec0f93713e30b923d9970d144af3e + firebase_analytics: 67fbdd9f3c04e55048024f3da21cfc36f05e56cf + firebase_auth: 9225db04db5d8e3b46dc8940e04bc6aec6833e27 + firebase_core: f1aafb21c14f497e5498f7ffc4dc63cbb52b2594 + firebase_crashlytics: c039028126cb45e32f4c217aa392408b0963d081 + firebase_storage: d558bbfa99449fff39352db83c466c8515fa1afa + FirebaseAnalytics: 0fc2b20091f0ddd21bf73397cf8f0eb5346dc24f + FirebaseAppCheckInterop: f734c802f21fe1da0837708f0f9a27218c8a4ed0 + FirebaseAuth: 4a2aed737c84114a9d9b33d11ae1b147d6b94889 + FirebaseAuthInterop: 858e6b754966e70740a4370dd1503dfffe6dbb49 + FirebaseCore: bb595f3114953664e3c1dc032f008a244147cfd3 + FirebaseCoreExtension: 7e1f7118ee970e001a8013719fb90950ee5e0018 + FirebaseCoreInternal: d7f5a043c2cd01a08103ab586587c1468047bca6 + FirebaseCrashlytics: a6ece278a837c7e88de2d9b5da0a3542f2342395 + FirebaseFirestore: 2a6183381cf7679b1bb000eb76a8e3178e25dee2 + FirebaseFirestoreInternal: 6577a27cd5dc3722b900042527f86d4ea1626134 + FirebaseInstallations: ae9f4902cb5bf1d0c5eaa31ec1f4e5495a0714e2 + FirebaseRemoteConfigInterop: 1e31ec72b89c9924367c59bfb5ec9ab60d1d6766 + FirebaseSessions: ba7c7a7ca8696a8d540eb3fe3800fbe98c79786d + FirebaseSharedSwift: 93426a1de92f19e1199fac5295a4f8df16458daa + FirebaseStorage: 20d6b56fb8a40ebaa03d6a2889fe33dac64adb73 + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + GoogleAdsOnDeviceConversion: e03a386840803ea7eef3fd22a061930142c039c1 + GoogleAppMeasurement: 1e718274b7e015cefd846ac1fcf7820c70dc017d + GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 + "gRPC-C++": cc207623316fb041a7a3e774c252cf68a058b9e8 + gRPC-Core: 860978b7db482de8b4f5e10677216309b5ff6330 + GTMSessionFetcher: 02d6e866e90bc236f48a703a041dfe43e6221a29 + leveldb-library: cc8b8f8e013647a295ad3f8cd2ddf49a6f19be19 + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 + RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4a5d436..fd06209 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -9,7 +9,10 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 341139FAC055BA9A06DCDCAF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5ED32C8606A316B7B31483E7 /* Pods_Runner.framework */; }; + 36FFEE815E5C239ECA957FD8 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CFAD14AFF1F90B8D3A191D09 /* Pods_RunnerTests.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 54FFB4BB9263E21E8BB6898C /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E55C273BDD75DB4F8B5AC5E9 /* GoogleService-Info.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; @@ -42,9 +45,12 @@ /* 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 = ""; }; + 219192A994BE2E810D21CF3E /* 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 = ""; }; 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 = ""; }; + 5ED32C8606A316B7B31483E7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6AF687E372B820F28E6339F0 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -55,13 +61,28 @@ 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 = ""; }; + 9F6D4F116EE51B9C9970A524 /* 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 = ""; }; + A3F457A9E261AAC5A4F510DE /* 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 = ""; }; + CFAD14AFF1F90B8D3A191D09 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D07CA0E6FCECE164629DBA3A /* 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 = ""; }; + E1FE5A7588919AB0262267D9 /* 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 = ""; }; + E55C273BDD75DB4F8B5AC5E9 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 95CA8DCA7A2925DEEFFC154C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 36FFEE815E5C239ECA957FD8 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 341139FAC055BA9A06DCDCAF /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -76,6 +97,15 @@ path = RunnerTests; sourceTree = ""; }; + 41888A3DB1CEF07094BFA7D0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5ED32C8606A316B7B31483E7 /* Pods_Runner.framework */, + CFAD14AFF1F90B8D3A191D09 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -94,6 +124,9 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + B4A1811494B588835818801A /* Pods */, + 41888A3DB1CEF07094BFA7D0 /* Frameworks */, + E55C273BDD75DB4F8B5AC5E9 /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -121,6 +154,19 @@ path = Runner; sourceTree = ""; }; + B4A1811494B588835818801A /* Pods */ = { + isa = PBXGroup; + children = ( + 219192A994BE2E810D21CF3E /* Pods-Runner.debug.xcconfig */, + E1FE5A7588919AB0262267D9 /* Pods-Runner.release.xcconfig */, + A3F457A9E261AAC5A4F510DE /* Pods-Runner.profile.xcconfig */, + 9F6D4F116EE51B9C9970A524 /* Pods-RunnerTests.debug.xcconfig */, + D07CA0E6FCECE164629DBA3A /* Pods-RunnerTests.release.xcconfig */, + 6AF687E372B820F28E6339F0 /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +174,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 1D789ECFE31345547CE34B50 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 95CA8DCA7A2925DEEFFC154C /* Frameworks */, ); buildRules = ( ); @@ -145,12 +193,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 1D144EDA24AD260BDCD8A5DC /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 3D0E01DF18316EDF773EAB0D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -216,12 +266,57 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + 54FFB4BB9263E21E8BB6898C /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1D144EDA24AD260BDCD8A5DC /* [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; + }; + 1D789ECFE31345547CE34B50 /* [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; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -238,6 +333,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 3D0E01DF18316EDF773EAB0D /* [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; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -251,7 +363,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -305,6 +417,7 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = A3F457A9E261AAC5A4F510DE /* Pods-Runner.profile.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -365,6 +478,7 @@ DEVELOPMENT_TEAM = CBCA453YCM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -379,6 +493,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 9F6D4F116EE51B9C9970A524 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,6 +511,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D07CA0E6FCECE164629DBA3A /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -411,6 +527,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 6AF687E372B820F28E6339F0 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -426,6 +543,7 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 219192A994BE2E810D21CF3E /* Pods-Runner.debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -483,6 +601,7 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = E1FE5A7588919AB0262267D9 /* Pods-Runner.release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -545,6 +664,7 @@ DEVELOPMENT_TEAM = CBCA453YCM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -568,6 +688,7 @@ DEVELOPMENT_TEAM = CBCA453YCM; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d4..2ee0045 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -73,6 +73,12 @@ ReferencedContainer = "container:Runner.xcodeproj"> + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index f9b0d7c..0000000 --- a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - PreviewsEnabled - - - diff --git a/lib/core/di/di.dart b/lib/core/di/di.dart new file mode 100644 index 0000000..44d6726 --- /dev/null +++ b/lib/core/di/di.dart @@ -0,0 +1,39 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:flutter/foundation.dart'; +import 'package:get_it/get_it.dart'; +import 'package:logger/logger.dart'; + +import '../services/network/network_service.dart'; +import '../services/network/network_service_impl.dart'; +import '../utils/analytics/app_analytics.dart'; +import '../utils/analytics/debug_analytics_impl.dart'; +import '../utils/analytics/firebase_analytics_impl.dart'; +import '../utils/logger/app_logger.dart'; +import '../utils/logger/app_logger_impl.dart'; +import '../utils/logger/logger_setup.dart'; + +/// Global instance of the GetIt service locator for dependency management. +final di = GetIt.instance; + +/// Initializes the application's dependencies using GetIt. +void setupDI() { + // Logger + di.registerLazySingleton(() => createLogger()); + di.registerLazySingleton(() => AppLoggerImpl(di())); + + // Firebase Analytics + di.registerLazySingleton(() => FirebaseAnalytics.instance); + di.registerLazySingleton( + () => kReleaseMode + ? FirebaseAnalyticsImpl(di()) + : DebugAnalyticsImpl(di()), + ); + + /// Connectivity + di.registerLazySingleton(() => Connectivity()); + di.registerLazySingleton( + () => NetworkServiceImpl(di()), + dispose: (param) => param.dispose(), + ); +} diff --git a/lib/core/failures/app_failure.dart b/lib/core/failures/app_failure.dart new file mode 100644 index 0000000..9e25e02 --- /dev/null +++ b/lib/core/failures/app_failure.dart @@ -0,0 +1,17 @@ +/// A base failure type used in the business logic layer. +/// +/// This class represents domain-level errors that wrap lower-level exceptions +/// and provide meaningful context for application logic. +abstract class AppFailure implements Exception { + /// A message describing the failure. + final String message; + + /// The original exception that caused this failure, if any. + final T? parentException; + + /// The stack trace at the point where the failure was created, if any. + final StackTrace? stackTrace; + + /// Creates an instance of [AppFailure]. + const AppFailure(this.parentException, this.message, this.stackTrace); +} diff --git a/lib/core/failures/feature/.gitkeep b/lib/core/failures/feature/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/core/failures/unknown_failure.dart b/lib/core/failures/unknown_failure.dart new file mode 100644 index 0000000..c47ec52 --- /dev/null +++ b/lib/core/failures/unknown_failure.dart @@ -0,0 +1,7 @@ +import 'app_failure.dart'; + +/// Unknown application error. +final class UnknownFailure extends AppFailure { + /// Creates an instance of [UnknownFailure]. + const UnknownFailure(super.parentException, super.message, super.stackTrace); +} diff --git a/lib/core/localization/.gitkeep b/lib/core/localization/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/core/result/async_result.dart b/lib/core/result/async_result.dart new file mode 100644 index 0000000..b5a096b --- /dev/null +++ b/lib/core/result/async_result.dart @@ -0,0 +1,5 @@ +import '../failures/app_failure.dart'; +import 'result.dart'; + +/// A type alias for an asynchronous [Result]. +typedef AsyncResult = Future>; diff --git a/lib/core/result/result.dart b/lib/core/result/result.dart new file mode 100644 index 0000000..2537c29 --- /dev/null +++ b/lib/core/result/result.dart @@ -0,0 +1,44 @@ +/// A sealed abstraction representing either a [Success] or a [Failure] result. +sealed class Result { + /// Creates a new [Result] instance. + const Result(); + + /// Creates a successful result containing the given [data]. + const factory Result.success(T data) = Success; + + /// Creates a failed result containing the given [error] and optional [stackTrace]. + const factory Result.failure(E error, [StackTrace? stackTrace]) = Failure; + + /// Whether this result represents a [Success]. + bool get isSuccess => this is Success; + + /// Whether this result represents a [Failure]. + bool get isFailure => this is Failure; + + /// Returns the success value if this is [Success], otherwise `null`. + T? get success => isSuccess ? (this as Success).data : null; + + /// Returns the failure value if this is [Failure], otherwise `null`. + E? get failure => isFailure ? (this as Failure).error : null; +} + +/// A successful result containing a value of type [T]. +final class Success extends Result { + /// The value returned in a successful result. + final T data; + + /// Creates a new [Success] containing the provided [data]. + const Success(this.data); +} + +/// A failed result containing an error of type [E] and optional stack trace. +final class Failure extends Result { + /// The error value describing the failure. + final E error; + + /// The associated stack trace for debugging, if available. + final StackTrace? stackTrace; + + /// Creates a new [Failure] with the given [error] and optional [stackTrace]. + const Failure(this.error, [this.stackTrace]); +} diff --git a/lib/core/router/analytics_route_observer.dart b/lib/core/router/analytics_route_observer.dart new file mode 100644 index 0000000..ecaa616 --- /dev/null +++ b/lib/core/router/analytics_route_observer.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +import '../utils/analytics/app_analytics.dart'; + +/// A universal navigation observer that sends screen view events +/// to the application's analytics abstraction. +class AnalyticsRouteObserver extends NavigatorObserver { + final AppAnalytics _analytics; + + /// Creates an instance of [AnalyticsRouteObserver]. + AnalyticsRouteObserver(this._analytics); + + @override + void didPush(Route route, Route? previousRoute) { + _logScreen(route); + super.didPush(route, previousRoute); + } + + @override + void didPop(Route route, Route? previousRoute) { + if (previousRoute != null) _logScreen(previousRoute); + + super.didPop(route, previousRoute); + } + + void _logScreen(Route route) { + final screenName = route.settings.name; + + if (screenName == null) return; + + _analytics.logScreenView(screenName); + } +} diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart new file mode 100644 index 0000000..4612b0c --- /dev/null +++ b/lib/core/router/router.dart @@ -0,0 +1,22 @@ +import 'package:go_router/go_router.dart'; + +import '../../features/debug/presentation/debug_screen.dart'; +import '../di/di.dart'; +import '../utils/analytics/app_analytics.dart'; +import 'analytics_route_observer.dart'; +import 'router_paths.dart'; + +/// The application's router using GoRouter. +final router = GoRouter( + initialLocation: AppRoutePaths.debugPath, + observers: [ + AnalyticsRouteObserver(di()), + ], + routes: [ + // Define your app's routes here + GoRoute( + path: AppRoutePaths.debugPath, + builder: (context, state) => const DebugScreen(), + ), + ], +); diff --git a/lib/core/router/router_paths.dart b/lib/core/router/router_paths.dart new file mode 100644 index 0000000..8cec8e7 --- /dev/null +++ b/lib/core/router/router_paths.dart @@ -0,0 +1,11 @@ +/// Contains all the route paths used for app navigation. +/// +/// Each path should be defined as a static constant string, for example: +/// ```dart +/// /// Description of the route. +/// static const String flowPath = 'flowPath'; +/// ``` +abstract class AppRoutePaths { + /// Route path for the debug screen. + static const debugPath = '/debug'; +} diff --git a/lib/core/services/.gitkeep b/lib/core/services/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/core/services/network/network_service.dart b/lib/core/services/network/network_service.dart new file mode 100644 index 0000000..3553987 --- /dev/null +++ b/lib/core/services/network/network_service.dart @@ -0,0 +1,17 @@ +/// Abstract interface for monitoring network availability. +abstract interface class NetworkService { + /// Stream that emits network availability changes. + Stream get connectionStream; + + /// Latest known network availability value. + bool get isConnected; + + /// Initializes the service, performing the initial connectivity check. + Future init(); + + /// Performs a one-shot check of current network availability. + Future hasNetwork(); + + /// Releases resources (stream subscription, controllers). + Future dispose(); +} diff --git a/lib/core/services/network/network_service_impl.dart b/lib/core/services/network/network_service_impl.dart new file mode 100644 index 0000000..742277b --- /dev/null +++ b/lib/core/services/network/network_service_impl.dart @@ -0,0 +1,53 @@ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; + +import 'network_service.dart'; + +/// Implementation of [NetworkService] using connectivity_plus package. +final class NetworkServiceImpl implements NetworkService { + final Connectivity _connectivity; + + final _controller = StreamController.broadcast(); + late final StreamSubscription> _subscription; + bool? _lastHasNetwork; + + /// Creates an instance of [NetworkServiceImpl]. + NetworkServiceImpl(this._connectivity) { + _subscription = _connectivity.onConnectivityChanged.listen(_onConnectivityChanged); + } + + void _onConnectivityChanged(List results) { + _emit(!results.contains(ConnectivityResult.none)); + } + + /// Emits [hasNetwork] to [connectionStream] when it changes. + void _emit(bool hasNetwork) { + if (_lastHasNetwork == hasNetwork) return; + _lastHasNetwork = hasNetwork; + if (!_controller.isClosed) _controller.add(hasNetwork); + } + + @override + Future init() async { + _emit(await hasNetwork()); + } + + @override + Future hasNetwork() async { + final results = await _connectivity.checkConnectivity(); + return !results.contains(ConnectivityResult.none); + } + + @override + Stream get connectionStream => _controller.stream; + + @override + bool get isConnected => _lastHasNetwork ?? false; + + @override + Future dispose() async { + await _subscription.cancel(); + await _controller.close(); + } +} diff --git a/lib/core/utils/analytics/app_analytics.dart b/lib/core/utils/analytics/app_analytics.dart new file mode 100644 index 0000000..c62ec37 --- /dev/null +++ b/lib/core/utils/analytics/app_analytics.dart @@ -0,0 +1,8 @@ +/// Abstract class for app analytics functionality. +abstract interface class AppAnalytics { + /// Logs an event with the given name and parameters. + Future logEvent(String name, {Map? parameters}); + + /// Logs a screen view with the given screen name. + Future logScreenView(String screenName); +} diff --git a/lib/core/utils/analytics/debug_analytics_impl.dart b/lib/core/utils/analytics/debug_analytics_impl.dart new file mode 100644 index 0000000..e7571c0 --- /dev/null +++ b/lib/core/utils/analytics/debug_analytics_impl.dart @@ -0,0 +1,17 @@ +import '../logger/app_logger.dart'; +import 'app_analytics.dart'; + +/// Implementation of [AppAnalytics]. +final class DebugAnalyticsImpl implements AppAnalytics { + final AppLogger _logger; + + /// Creates an instance of [DebugAnalyticsImpl]. + DebugAnalyticsImpl(this._logger); + + @override + Future logEvent(String name, {Map? parameters}) async => + _logger.i('Event: $name, params: $parameters'); + + @override + Future logScreenView(String screenName) async => _logger.i('ScreenView: $screenName'); +} diff --git a/lib/core/utils/analytics/firebase_analytics_impl.dart b/lib/core/utils/analytics/firebase_analytics_impl.dart new file mode 100644 index 0000000..8fadf8c --- /dev/null +++ b/lib/core/utils/analytics/firebase_analytics_impl.dart @@ -0,0 +1,19 @@ +import 'package:firebase_analytics/firebase_analytics.dart'; + +import 'app_analytics.dart'; + +/// Implementation of [AppAnalytics]. +final class FirebaseAnalyticsImpl implements AppAnalytics { + final FirebaseAnalytics _analytics; + + /// Creates an instance of [FirebaseAnalyticsImpl]. + FirebaseAnalyticsImpl(this._analytics); + + @override + Future logEvent(String name, {Map? parameters}) async => + await _analytics.logEvent(name: name, parameters: parameters); + + @override + Future logScreenView(String screenName) async => + await _analytics.logScreenView(screenName: screenName); +} diff --git a/lib/core/utils/logger/app_logger.dart b/lib/core/utils/logger/app_logger.dart new file mode 100644 index 0000000..f10fa74 --- /dev/null +++ b/lib/core/utils/logger/app_logger.dart @@ -0,0 +1,20 @@ +/// Abstract class for application logging. +abstract interface class AppLogger { + /// Log a message at trace level. + void t(String message, [Object? error, StackTrace? stackTrace]); + + /// Log a message at debug level. + void d(String message, [Object? error, StackTrace? stackTrace]); + + /// Log a message at info level. + void i(String message, [Object? error, StackTrace? stackTrace]); + + /// Log a message at warning level. + void w(String message, [Object? error, StackTrace? stackTrace]); + + /// Log a message at error level. + void e(String message, [Object? error, StackTrace? stackTrace]); + + /// Log a message at fatal level. + void f(String message, [Object? error, StackTrace? stackTrace]); +} diff --git a/lib/core/utils/logger/app_logger_impl.dart b/lib/core/utils/logger/app_logger_impl.dart new file mode 100644 index 0000000..879e7aa --- /dev/null +++ b/lib/core/utils/logger/app_logger_impl.dart @@ -0,0 +1,41 @@ +import 'package:logger/logger.dart'; + +import 'app_logger.dart'; + +/// Implementation of [AppLogger]. +final class AppLoggerImpl implements AppLogger { + final Logger _logger; + + /// Creates an instance of [AppLoggerImpl] with the provided [Logger]. + AppLoggerImpl(this._logger); + + @override + void t(String message, [Object? error, StackTrace? stackTrace]) { + _logger.t(message, error: error, stackTrace: stackTrace); + } + + @override + void d(String message, [Object? error, StackTrace? stackTrace]) { + _logger.d(message, error: error, stackTrace: stackTrace); + } + + @override + void i(String message, [Object? error, StackTrace? stackTrace]) { + _logger.i(message, error: error, stackTrace: stackTrace); + } + + @override + void w(String message, [Object? error, StackTrace? stackTrace]) { + _logger.w(message, error: error, stackTrace: stackTrace); + } + + @override + void e(String message, [Object? error, StackTrace? stackTrace]) { + _logger.e(message, error: error, stackTrace: stackTrace); + } + + @override + void f(String message, [Object? error, StackTrace? stackTrace]) { + _logger.f(message, error: error, stackTrace: stackTrace); + } +} diff --git a/lib/core/utils/logger/logger_mixin.dart b/lib/core/utils/logger/logger_mixin.dart new file mode 100644 index 0000000..45ec89d --- /dev/null +++ b/lib/core/utils/logger/logger_mixin.dart @@ -0,0 +1,12 @@ +import '../../di/di.dart'; +import 'app_logger.dart'; + +/// Mixin for convenient access to AppLogger through GetIt. +/// +/// Used in Bloc/Cubit for debug logging. +/// For critical components (Repository, UseCase), dependency injection +/// through the constructor is preferable for easier testing. +mixin AppLoggerMixin { + /// Gets the [AppLogger] instance from the global DI container. + AppLogger get logger => di(); +} diff --git a/lib/core/utils/logger/logger_setup.dart b/lib/core/utils/logger/logger_setup.dart new file mode 100644 index 0000000..6bb7a2e --- /dev/null +++ b/lib/core/utils/logger/logger_setup.dart @@ -0,0 +1,24 @@ +import 'package:flutter/foundation.dart'; +import 'package:logger/logger.dart'; + +/// Creates and configures the [Logger] instance based on the environment. +Logger createLogger() { + const isRelease = kReleaseMode; + final printer = isRelease + ? PrettyPrinter( + methodCount: 0, + errorMethodCount: 5, + lineLength: 100, + colors: false, + printEmojis: false, + ) + : PrettyPrinter( + errorMethodCount: 5, + lineLength: 100, + colors: false, + ); + return Logger( + level: isRelease ? Level.warning : Level.debug, + printer: printer, + ); +} diff --git a/lib/features/app/app.dart b/lib/features/app/app.dart new file mode 100644 index 0000000..d721fa9 --- /dev/null +++ b/lib/features/app/app.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +import '../../core/router/router.dart'; + +/// The main application widget. +class FlutterStarterTemplate extends StatelessWidget { + /// Creates a [FlutterStarterTemplate] widget. + const FlutterStarterTemplate({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router(routerConfig: router); + } +} diff --git a/lib/features/auth/data/mappers/.gitkeep b/lib/features/auth/data/mappers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/data/models/.gitkeep b/lib/features/auth/data/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/data/repositories/.gitkeep b/lib/features/auth/data/repositories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/data/sources/.gitkeep b/lib/features/auth/data/sources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/domain/entities/.gitkeep b/lib/features/auth/domain/entities/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/domain/repositories/.gitkeep b/lib/features/auth/domain/repositories/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/domain/usecases/.gitkeep b/lib/features/auth/domain/usecases/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/presentation/pages/.gitkeep b/lib/features/auth/presentation/pages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/auth/presentation/widgets/.gitkeep b/lib/features/auth/presentation/widgets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/features/debug/presentation/debug_screen.dart b/lib/features/debug/presentation/debug_screen.dart new file mode 100644 index 0000000..f3276e4 --- /dev/null +++ b/lib/features/debug/presentation/debug_screen.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +/// A screen for debugging purposes. +class DebugScreen extends StatelessWidget { + /// Creates a [DebugScreen]. + const DebugScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: Text('Flutter Starter Template'), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 640824f..acb02c6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,3 @@ -import 'package:flutter/material.dart'; +import 'runner.dart'; -void main() { - runApp(const MainApp()); -} - -class MainApp extends StatelessWidget { - const MainApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold(body: Center(child: Text('Hello World!'))), - ); - } -} +Future main() async => await run(); diff --git a/lib/runner.dart b/lib/runner.dart new file mode 100644 index 0000000..be468c6 --- /dev/null +++ b/lib/runner.dart @@ -0,0 +1,59 @@ +import 'dart:async'; + +import 'package:firebase_analytics/firebase_analytics.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'core/di/di.dart'; +import 'core/utils/logger/app_logger.dart'; +import 'features/app/app.dart'; +import 'firebase_options.dart'; + +/// The main entry point of the application. +Future run() async { + await runZonedGuarded>( + () async { + WidgetsFlutterBinding.ensureInitialized(); + + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(kReleaseMode); + await FirebaseAnalytics.instance.setAnalyticsCollectionEnabled(true); + + // Setup dependency injection + setupDI(); + final logger = di(); + + // Catch errors from Flutter framework + FlutterError.onError = (details) { + logger.e('FlutterError', details.exception, details.stack); + FirebaseCrashlytics.instance.recordFlutterError(details); + }; + + // Catch errors from outside Flutter framework + PlatformDispatcher.instance.onError = (error, stack) { + logger.f('PlatformDispatcherError', error, stack); + FirebaseCrashlytics.instance.recordError(error, stack); + return true; + }; + + runApp(const FlutterStarterTemplate()); + }, + (error, stackTrace) { + // Fallback handler (without DI) + debugPrint('UncaughtZoneError: $error'); + debugPrintStack(stackTrace: stackTrace); + + try { + FirebaseCrashlytics.instance.recordError(error, stackTrace); + } catch (e, s) { + // In case Firebase/Crashlytics aren't ready yet + debugPrint('Error while reporting to Crashlytics: $e'); + debugPrintStack(stackTrace: s); + } + }, + ); +} diff --git a/lib/uikit/buttons/.gitkeep b/lib/uikit/buttons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/uikit/images/.gitkeep b/lib/uikit/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/uikit/themes/colors/.gitkeep b/lib/uikit/themes/colors/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/uikit/themes/text/.gitkeep b/lib/uikit/themes/text/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/pubspec.lock b/pubspec.lock index 0638fc1..c9372de 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,141 +1,1095 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" + source: hosted + version: "91.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: e4a1b612fd2955908e26116075b3a4baf10c353418ca645b4deae231c82bf144 + url: "https://pub.dev" + source: hosted + version: "1.3.65" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" + source: hosted + version: "8.4.1" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + 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: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: a2cebb899f91d36eeeaa55c7b20b5915db5a9df1b8fd4a3c9c825e22e474537d + url: "https://pub.dev" + source: hosted + version: "9.1.0" + bloc_test: + dependency: "direct main" + description: + name: bloc_test + sha256: "1dd549e58be35148bc22a9135962106aa29334bc1e3f285994946a1057b29d7b" + url: "https://pub.dev" + source: hosted + version: "10.0.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: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413 + url: "https://pub.dev" + source: hosted + version: "4.0.3" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057" + url: "https://pub.dev" + source: hosted + version: "2.10.4" + 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: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139" + url: "https://pub.dev" + source: hosted + version: "8.12.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "88abd6dc7786c23c8b5e434901bb0d79176414e52e9ab50a8e52552ff6148d7a" + url: "https://pub.dev" + source: hosted + version: "6.1.1" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "573d4b4ebc56ba573dc9bac88b65bdb991cc5b66a885a62c7ab8dd1e2eaa0944" + url: "https://pub.dev" + source: hosted + version: "7.0.5" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: c1ef53308a09d475503f75b658b65fae32af24047d03bba0713098f882ed187f + url: "https://pub.dev" + source: hosted + version: "5.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + url: "https://pub.dev" + source: hosted + version: "4.11.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b + url: "https://pub.dev" + source: hosted + version: "3.1.3" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" + dio: + dependency: "direct main" + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + firebase_analytics: + dependency: "direct main" + description: + name: firebase_analytics + sha256: "8ca4832c7a6d145ce987fd07d6dfbb8c91d9058178342f20de6305fb77b1b40d" + url: "https://pub.dev" + source: hosted + version: "12.1.0" + firebase_analytics_platform_interface: + dependency: transitive + description: + name: firebase_analytics_platform_interface + sha256: d00234716f415f89eb5c2cefb1238d7fd2f3120275d71414b84ae434dcdb7a19 + url: "https://pub.dev" + source: hosted + version: "5.0.5" + firebase_analytics_web: + dependency: transitive + description: + name: firebase_analytics_web + sha256: e42b294e51aedb4bd4b761a886c8d6b473c44b44aa4c0b47cab06b2c66ac3fba + url: "https://pub.dev" + source: hosted + version: "0.6.1+1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "060a9bfc9877538dfdc69f2fdc1e0bf068439a7e9f22da6513f7b9bde308bb93" + url: "https://pub.dev" + source: hosted + version: "6.1.3" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: e91c9aadf9944e8319855fa752fd6c664bade2bbd6e7697a5142113c319a4288 + url: "https://pub.dev" + source: hosted + version: "8.1.5" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "155145fd84f311e50eb2bfeb892f74b0d1b30936f40765a051b70110270cb6d1" + url: "https://pub.dev" + source: hosted + version: "6.1.1" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "29cfa93c771d8105484acac340b5ea0835be371672c91405a300303986f4eba9" + url: "https://pub.dev" + source: hosted + version: "4.3.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: a631bbfbfa26963d68046aed949df80b228964020e9155b086eff94f462bbf1f + url: "https://pub.dev" + source: hosted + version: "3.3.1" + firebase_crashlytics: + dependency: "direct main" + description: + name: firebase_crashlytics + sha256: "8d52022ee6fdd224e92c042f297d1fd0ec277195c49f39fa61b8cc500a639f00" + url: "https://pub.dev" + source: hosted + version: "5.0.6" + firebase_crashlytics_platform_interface: + dependency: transitive + description: + name: firebase_crashlytics_platform_interface + sha256: "97c6a97b35e3d3dafe38fb053a65086a1efb125022d292161405848527cc25a4" + url: "https://pub.dev" + source: hosted + version: "3.8.16" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + sha256: "5321a88518d992d03f8482bc48ede9cfb315b465bd59f1587468bc2f4a809d08" + url: "https://pub.dev" + source: hosted + version: "13.0.5" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: dbcd5363a6b2edf2d1238fc22f10f4a39f0ea991a432b4fc43871ea31647cf5a + url: "https://pub.dev" + source: hosted + version: "5.2.16" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: "141ada1185fbd4a982d7cdd4486d1e42fe616db6024e22f7fcb3e9df388be8b4" + url: "https://pub.dev" + source: hosted + version: "3.11.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77" + url: "https://pub.dev" + source: hosted + version: "3.2.3" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: "1d648d2dd2047d7f7450d5727ca24ee435f240385753d90b49650e3cdff32e56" + url: "https://pub.dev" + source: hosted + version: "9.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: eff94d2a6fc79fa8b811dde79c7549808c2346037ee107a1121b4a644c745f2a + url: "https://pub.dev" + source: hosted + version: "17.0.1" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.dev" + source: hosted + version: "4.3.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.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" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + 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: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 + url: "https://pub.dev" + source: hosted + version: "6.11.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lean_builder: + dependency: transitive + description: + name: lean_builder + sha256: "4f3d70c34c52cc5034e8cc6f53d35aa3a32fb373b78fb4c29cf45cd1dcf06942" + url: "https://pub.dev" + source: hosted + version: "0.1.5" + lints: + dependency: transitive + description: + name: lints + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + logger: + dependency: "direct main" + description: + name: logger + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 + url: "https://pub.dev" + source: hosted + version: "2.6.2" + 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: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mockito: + dependency: "direct main" + description: + name: mockito + sha256: dac24d461418d363778d53198d9ac0510b9d073869f078450f195766ec48d05e + url: "https://pub.dev" + source: hosted + version: "5.6.1" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + native_splash_screen: + dependency: "direct main" + description: + name: native_splash_screen + sha256: "346f713538ccfb5dd0723e7f699985d64468aabe776de8b544804de253467446" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + native_splash_screen_linux: + dependency: transitive + description: + name: native_splash_screen_linux + sha256: e8b6ecc6a2cd9e5c29a57eace024f02e00207378f824a645bf4d5248e1655117 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + native_splash_screen_macos: + dependency: transitive + description: + name: native_splash_screen_macos + sha256: cae3c86844d9ebb362c2437a234cffd4839d9ecbe58cf74352b6574c29c3078a + url: "https://pub.dev" + source: hosted + version: "3.0.0" + native_splash_screen_platform_interface: + dependency: transitive + description: + name: native_splash_screen_platform_interface + sha256: d1ba329116ed47dd6422442beaad1cc3ea4f7c2d926c4450b946a959de80d446 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + native_splash_screen_windows: + dependency: transitive + description: + name: native_splash_screen_windows + sha256: "9c9f5130b3fc9b0300d564270517ce79530fb04d69b7f7c030ab8678e04e2834" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" url: "https://pub.dev" source: hosted - version: "2.13.0" - boolean_selector: + version: "2.0.2" + octo_image: dependency: transitive description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + url: "https://pub.dev" + source: hosted + version: "2.2.22" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted version: "2.1.2" - characters: + path_provider_windows: dependency: transitive description: - name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "1.4.0" - clock: + version: "2.3.0" + petitparser: dependency: transitive description: - name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "1.1.2" - collection: + version: "7.0.1" + platform: dependency: transitive description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "1.19.1" - fake_async: + version: "3.1.6" + plugin_platform_interface: dependency: transitive description: - name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "1.3.3" - flutter: + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "2fcc8a202ca7ec17dab7c97d6b6d91cf03aa07fe6f65f8afbb6dfa52cc5bd902" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + provider: dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" description: - name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.0.0" - flutter_test: + version: "6.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" + retrofit: + dependency: "direct main" + description: + name: retrofit + sha256: "84063c18a00d55af41d6b8401edf8473e8c215bd7068ef7ec5e34c60657ffdbe" + url: "https://pub.dev" + source: hosted + version: "4.9.1" + retrofit_generator: dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - leak_tracker: + description: + name: retrofit_generator + sha256: "7ec323f3329ad2ca0bcdc96fe02ec7f2486ecfac6cd2d035b03c398ef6f42308" + url: "https://pub.dev" + source: hosted + version: "10.2.0" + rxdart: dependency: transitive description: - name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" url: "https://pub.dev" source: hosted - version: "11.0.2" - leak_tracker_flutter_testing: + version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: dependency: transitive description: - name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + name: shared_preferences_android + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" url: "https://pub.dev" source: hosted - version: "3.0.10" - leak_tracker_testing: + version: "2.4.18" + shared_preferences_foundation: dependency: transitive description: - name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "3.0.2" - lints: + version: "2.5.6" + shared_preferences_linux: dependency: transitive description: - name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "6.0.0" - matcher: + version: "2.4.1" + shared_preferences_platform_interface: dependency: transitive description: - name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "0.12.17" - material_color_utilities: + version: "2.4.1" + shared_preferences_web: dependency: transitive description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "0.11.1" - meta: + version: "2.4.3" + shared_preferences_windows: dependency: transitive description: - name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "1.16.0" - path: + version: "2.4.1" + shelf: dependency: transitive description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" + url: "https://pub.dev" + source: hosted + version: "1.3.8" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -144,6 +1098,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -160,6 +1154,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -168,6 +1170,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -176,14 +1186,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" + source: hosted + version: "0.6.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "4.5.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + url: "https://pub.dev" + source: hosted + version: "1.1.19" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" + url: "https://pub.dev" + source: hosted + version: "1.1.13" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + url: "https://pub.dev" + source: hosted + version: "1.1.19" vector_math: dependency: transitive description: @@ -200,6 +1266,78 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: f52385d4f73589977c80797e60fe51014f7f2b957b5e9a62c3f6ada439889249 + url: "https://pub.dev" + source: hosted + version: "1.2.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: - dart: ">=3.9.2 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index ae3fa28..1d83386 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,18 +1,61 @@ name: flutter_starter_template description: A scalable Flutter Starter Template for building production-ready business applications. publish_to: 'none' -version: 0.1.0 +version: 0.2.0 environment: - sdk: ^3.9.2 + sdk: '>=3.10.0 <4.0.0' license: "MIT" dependencies: + bloc_test: ^10.0.0 + cached_network_image: ^3.4.1 + cloud_firestore: ^6.1.1 + connectivity_plus: ^7.0.0 + dio: ^5.9.0 + equatable: ^2.0.7 + firebase_analytics: ^12.1.0 + firebase_auth: ^6.1.3 + firebase_core: ^4.3.0 + firebase_crashlytics: ^5.0.6 + firebase_storage: ^13.0.5 flutter: sdk: flutter + flutter_bloc: ^9.1.1 + flutter_localizations: + sdk: flutter + flutter_svg: ^2.2.3 + freezed_annotation: ^3.1.0 + get_it: ^9.2.0 + go_router: ^17.0.1 + intl: ^0.20.2 + json_annotation: ^4.9.0 + logger: ^2.6.2 + mockito: ^5.6.1 + native_splash_screen: ^3.0.0 + path: ^1.9.1 + path_provider: ^2.1.5 + provider: ^6.1.5+1 + retrofit: ^4.9.1 + shared_preferences: ^2.5.4 dev_dependencies: + build_runner: ^2.10.4 + freezed: ^3.2.3 + flutter_lints: ^6.0.0 flutter_test: sdk: flutter - flutter_lints: ^6.0.0 + json_serializable: ^6.11.1 + retrofit_generator: ^10.2.0 + +flutter: + uses-material-design: true + generate: true + + assets: + - assets/icons/ + - assets/images/ + - assets/splash/ + + # fonts: \ No newline at end of file diff --git a/test/core/router/analytics_route_onserver_test.dart b/test/core/router/analytics_route_onserver_test.dart new file mode 100644 index 0000000..3424a17 --- /dev/null +++ b/test/core/router/analytics_route_onserver_test.dart @@ -0,0 +1,88 @@ +// ignore_for_file: unawaited_futures + +import 'package:flutter/material.dart'; +import 'package:flutter_starter_template/core/router/analytics_route_observer.dart'; +import 'package:flutter_starter_template/core/utils/analytics/app_analytics.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'analytics_route_onserver_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + late AppAnalytics mockAnalytics; + late AnalyticsRouteObserver analyticsRouteObserver; + + Future pumpNavigator(WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + navigatorObservers: [analyticsRouteObserver], + home: const Scaffold(body: Text('Home Screen')), + routes: {'/test': (context) => const Scaffold(body: Text('Test Screen'))}, + ), + ); + } + + setUp(() { + mockAnalytics = MockAppAnalytics(); + analyticsRouteObserver = AnalyticsRouteObserver(mockAnalytics); + }); + + group('AnalyticsRouteObserver', () { + group('didPush', () { + testWidgets('should log screen view when pushing a named route', (tester) async { + // Arrange + await pumpNavigator(tester); + clearInteractions(mockAnalytics); + + // Act + final context = tester.element(find.text('Home Screen')); + Navigator.pushNamed(context, '/test'); + await tester.pumpAndSettle(); + + // Assert + verify(mockAnalytics.logScreenView('/test')).called(1); + }); + + testWidgets('should not log screen view when pushing a route without name', (tester) async { + // Arrange + await pumpNavigator(tester); + clearInteractions(mockAnalytics); + + // Act + final context = tester.element(find.text('Home Screen')); + Navigator.push(context, MaterialPageRoute(builder: (context) => const SizedBox())); + await tester.pumpAndSettle(); + + // Assert + verifyNever(mockAnalytics.logScreenView('/test')); + }); + }); + + group('didPop', () { + testWidgets('should log previous route name when popping current route', (tester) async { + // Arrange + await pumpNavigator(tester); + final context = tester.element(find.text('Home Screen')); + Navigator.pushNamed(context, '/test'); + await tester.pumpAndSettle(); + clearInteractions(mockAnalytics); + + // Act + Navigator.pop(context, tester.element(find.text('Test Screen'))); + await tester.pumpAndSettle(); + + // Assert + verify(mockAnalytics.logScreenView('/')).called(1); + }); + }); + + group('Initialization', () { + testWidgets('should log initial route on app start', (tester) async { + await pumpNavigator(tester); + verify(mockAnalytics.logScreenView('/')).called(1); + }); + }); + }); +} diff --git a/test/core/services/network/network_service_test.dart b/test/core/services/network/network_service_test.dart new file mode 100644 index 0000000..5392946 --- /dev/null +++ b/test/core/services/network/network_service_test.dart @@ -0,0 +1,165 @@ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter_starter_template/core/services/network/network_service_impl.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'network_service_test.mocks.dart'; + +@GenerateNiceMocks([MockSpec()]) +void main() { + late Connectivity connectivity; + + setUp(() { + connectivity = MockConnectivity(); + + when( + connectivity.onConnectivityChanged, + ).thenAnswer((_) => const Stream>.empty()); + }); + + group('NetworkServiceImpl', () { + test('init sets isConnected=true when network is available (wifi)', () async { + // Arrange + when( + connectivity.checkConnectivity(), + ).thenAnswer((_) async => [ConnectivityResult.wifi]); + + final service = NetworkServiceImpl(connectivity); + addTearDown(() async => await service.dispose()); + + // Act + await service.init(); + + // Assert + expect(service.isConnected, isTrue); + verify(connectivity.checkConnectivity()).called(1); + }); + + test('init sets isConnected=false when no network is available (none)', () async { + // Arrange + when( + connectivity.checkConnectivity(), + ).thenAnswer((_) async => [ConnectivityResult.none]); + + final service = NetworkServiceImpl(connectivity); + addTearDown(() async => await service.dispose()); + + // Act + await service.init(); + + // Assert + expect(service.isConnected, isFalse); + verify(connectivity.checkConnectivity()).called(1); + }); + + test( + 'connectionStream emits true when connectivity changes to wifi', + () async { + // Arrange + final controller = StreamController>(); + addTearDown(() async => await controller.close()); + + when(connectivity.onConnectivityChanged).thenAnswer((_) => controller.stream); + + final service = NetworkServiceImpl(connectivity); + addTearDown(() async => await service.dispose()); + + final events = []; + final sub = service.connectionStream.listen(events.add); + addTearDown(() async => await sub.cancel()); + + // Act + controller.add([ConnectivityResult.wifi]); + await pumpEventQueue(); + + // Assert + expect(events, [true]); + }, + ); + + test( + 'connectionStream emits values in order for multiple connectivity changes', + () async { + // Arrange + final controller = StreamController>(); + + addTearDown(() async => await controller.close()); + when(connectivity.onConnectivityChanged).thenAnswer((_) => controller.stream); + + final service = NetworkServiceImpl(connectivity); + addTearDown(() async => await service.dispose()); + + final events = []; + final sub = service.connectionStream.listen(events.add); + addTearDown(() async => await sub.cancel()); + + // Act + controller.add([ConnectivityResult.none]); + await pumpEventQueue(); + + controller.add([ConnectivityResult.wifi]); + await pumpEventQueue(); + + controller.add([ConnectivityResult.none]); + await pumpEventQueue(); + + // Assert + expect(events, [false, true, false]); + }, + ); + + test('connectionStream does not emit duplicates when state does not change', () async { + // Arrange + final controller = StreamController>(); + addTearDown(() async => await controller.close()); + + when(connectivity.onConnectivityChanged).thenAnswer((_) => controller.stream); + + final service = NetworkServiceImpl(connectivity); + addTearDown(() async => await service.dispose()); + + final events = []; + final sub = service.connectionStream.listen(events.add); + addTearDown(() async => await sub.cancel()); + + // Act + controller.add([ConnectivityResult.wifi]); + await pumpEventQueue(); + + controller.add([ConnectivityResult.mobile]); + await pumpEventQueue(); + + controller.add([ConnectivityResult.wifi]); + await pumpEventQueue(); + + // Assert + expect(events, [true]); + }); + + test('dispose cancels subscription and stops emitting further updates', () async { + // Arrange + final controller = StreamController>(); + addTearDown(() async => await controller.close()); + + when(connectivity.onConnectivityChanged).thenAnswer((_) => controller.stream); + + final service = NetworkServiceImpl(connectivity); + + final events = []; + final subscription = service.connectionStream.listen(events.add); + addTearDown(() async => await subscription.cancel()); + + // Act + await service.dispose(); + + controller.add([ConnectivityResult.wifi]); + await pumpEventQueue(); + + // Assert + expect(events, isEmpty); + }); + }); +}