commit 577c4b33b7958533ab1a51f9889dd96c85506871 Author: Rijad Zuzo Date: Sat Feb 14 20:10:16 2026 +0100 Scaffold backend gateway and integration docs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..52e942a --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + - platform: android + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + - platform: ios + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + - platform: linux + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + - platform: macos + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + - platform: web + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + - platform: windows + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/README.md b/README.md new file mode 100644 index 0000000..b93c219 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# relationship_saver + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..8a55f7d --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,19 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + always_declare_return_types: true + always_use_package_imports: true + avoid_dynamic_calls: true + avoid_print: true + directives_ordering: true + prefer_final_fields: true + prefer_final_locals: true + sort_constructors_first: true + unawaited_futures: true diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..f5ad208 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.relationship_saver" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.relationship_saver" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9521e63 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/relationship_saver/MainActivity.kt b/android/app/src/main/kotlin/com/example/relationship_saver/MainActivity.kt new file mode 100644 index 0000000..523ccdd --- /dev/null +++ b/android/app/src/main/kotlin/com/example/relationship_saver/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.relationship_saver + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/docs/ADR/0002-backend-protocol-rest-openapi.md b/docs/ADR/0002-backend-protocol-rest-openapi.md new file mode 100644 index 0000000..4d2b7c0 --- /dev/null +++ b/docs/ADR/0002-backend-protocol-rest-openapi.md @@ -0,0 +1,57 @@ +# ADR 0002: Backend Protocol = HTTPS REST + JSON (OpenAPI-First) + +- Status: Accepted +- Date: 2026-02-14 + +## Context + +`relationship_saver` is offline-first for MVP. Local DB remains the source of truth for core usage. Backend responsibilities are incremental: + +1. account/session management +2. sync transport for local mutations and remote updates +3. recommendation/signals feed +4. future upload/download URL broker + +We need a transport that is cross-platform, testable in Flutter, easy to stub for local development, and contract-driven across mobile/backend teams. + +## Decision + +Use HTTPS REST + JSON with an OpenAPI-first contract. + +- Client HTTP stack: `dio` +- Typed models: `freezed` + `json_serializable` +- Gateway boundary: `BackendGateway` (transport-agnostic interface) +- Runtime implementation: `BackendGatewayRest` +- Local development implementation: `BackendGatewayFake` +- Auth/session support: access + refresh token with secure storage +- Retry policy: only transient failures; non-idempotent retries require idempotency key +- Error surface: typed `BackendException` taxonomy + +## Consequences + +### Positive + +- Strong, versioned contract artifact in `docs/api/openapi.yaml` +- Fast local iteration with fake gateway, no backend dependency for core UX +- Explicit behavior for refresh/retry/failure classes +- Good compatibility across Android/iOS/macOS/Windows/Linux/Web + +### Tradeoffs + +- REST polling for feed/sync may be less real-time than socket-based push +- Some envelope sizes may require tuning and pagination strategy +- Requires careful idempotency handling for mutation endpoints + +## Evolution Plan + +Realtime is deferred for now but planned: + +1. Keep REST as source of truth for command/query semantics. +2. Add optional SSE or WebSocket channel for push notifications/signals invalidation. +3. Continue using same DTO schema envelopes from OpenAPI where possible. +4. Add explicit capability negotiation (`/v1/capabilities`) when realtime ships. + +## Rejected Alternatives + +- gRPC-only for MVP: stronger streaming story, but higher setup and broader tooling burden for immediate scope. +- GraphQL-first: flexible querying, but sync/mutation idempotency patterns are clearer with explicit REST resources now. diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md new file mode 100644 index 0000000..52b4a1f --- /dev/null +++ b/docs/PROGRESS.md @@ -0,0 +1,203 @@ +# Relationship Saver Progress Log + +Updated: 2026-02-14 + +## Current Status + +The Flutter app now includes a production-grade backend integration scaffold with an offline-first posture: + +- Local/offline UX remains primary; backend is optional for core usage. +- A transport-agnostic gateway interface is in place. +- REST implementation with auth refresh/retry/error mapping is implemented. +- Deterministic fake gateway is available for local/dev without backend. +- OpenAPI contract + ADR + setup docs + unit tests are in place. +- `flutter analyze` and `flutter test` are passing. + +## What Was Implemented + +### 1. Core Config + Theme Shell + +- Added backend config with build-time + runtime override: + - `lib/core/config/app_config.dart` +- Added lightweight app theme aligned with reference style direction: + - `lib/core/config/app_theme.dart` +- Updated app entry shell and Riverpod root: + - `lib/main.dart` + +### 2. Network Foundation + +Implemented a Dio stack with request metadata, auth, refresh, retry, and error mapping: + +- `lib/core/network/dio_factory.dart` +- `lib/core/network/network_constants.dart` +- `lib/core/network/backend_exception.dart` +- `lib/core/network/dio_error_mapper.dart` +- Interceptors: + - `lib/core/network/interceptors/request_metadata_interceptor.dart` + - `lib/core/network/interceptors/auth_interceptor.dart` + - `lib/core/network/interceptors/refresh_token_interceptor.dart` + - `lib/core/network/interceptors/retry_interceptor.dart` + +Behavior implemented: + +- Adds per-request `X-Request-Id`. +- Adds `Authorization: Bearer ` when session exists. +- On `401`, attempts one refresh (`/v1/auth/refresh`) then retries original request. +- If refresh fails, session is cleared and `AuthExpiredException` surfaced. +- Retry policy (max 2 retries, exponential backoff): + - Retry transient failures (`timeouts`, `connection`, `5xx`). + - Safe methods (`GET/HEAD/OPTIONS`) retry automatically. + - Non-idempotent endpoints only retry when `Idempotency-Key` exists. +- Maps Dio failures into typed backend exceptions with metadata. + +### 3. Auth Token Storage + +- Interface: + - `lib/core/auth/token_store.dart` +- Production implementation: + - `lib/core/auth/secure_token_store.dart` +- Test/dev implementation: + - `lib/core/auth/in_memory_token_store.dart` + +### 4. Backend Integration Layer + +Domain boundary and implementations: + +- Interface: + - `lib/integrations/backend/backend_gateway.dart` +- REST: + - `lib/integrations/backend/backend_gateway_rest.dart` +- Fake: + - `lib/integrations/backend/backend_gateway_fake.dart` +- Optional Riverpod providers: + - `lib/integrations/backend/backend_gateway_provider.dart` +- Sync validation: + - `lib/integrations/backend/sync_envelope_validator.dart` + +Implemented methods: + +- sign-in/out + me +- sync pull/push (with idempotency key on push) +- signals feed + ack +- upload init + download URL scaffold + +Offline-first hardening: + +- `signOut()` clears local token store in `finally`, even when network call fails. + +### 5. Typed Models (Freezed + JSON) + +- `lib/integrations/backend/models/backend_models.dart` +- Generated files: + - `backend_models.freezed.dart` + - `backend_models.g.dart` + +Includes DTOs/enums for: + +- Auth (`SignInRequest`, `AuthSession`, refresh request/response, `UserProfile`) +- Sync (`ChangeEnvelope`, push/pull requests/results, ack/rejection) +- Signals (`SignalsFeed`, `SignalItem`, ack request/action) +- Uploads (`UploadInitRequest/Result`, `DownloadUrl`) + +### 6. Contract + Architecture Docs + +- OpenAPI spec: + - `docs/api/openapi.yaml` +- ADR: + - `docs/ADR/0002-backend-protocol-rest-openapi.md` +- Setup instructions: + - `docs/SETUP.md` +- Previous concise checklist log: + - `docs/PROGRESS.md` + +### 7. Tooling + Quality + +- Strict analyzer/lints: + - `analysis_options.yaml` +- Dependencies added in `pubspec.yaml`: + - runtime: `dio`, `freezed_annotation`, `json_annotation`, `flutter_secure_storage`, `uuid`, `clock`, `flutter_riverpod` + - dev: `freezed`, `json_serializable`, `build_runner`, `mocktail` + +### 8. Test Coverage Added + +Core network and model tests: + +- `test/core/network/token_refresh_interceptor_test.dart` +- `test/core/network/dio_error_mapper_test.dart` +- `test/core/network/retry_interceptor_test.dart` +- `test/integrations/backend/backend_models_serialization_test.dart` +- `test/integrations/backend/sync_envelope_validator_test.dart` +- helper adapter: `test/helpers/queue_http_client_adapter.dart` +- updated smoke widget test: `test/widget_test.dart` + +## Verification Snapshot + +Most recent local checks: + +- `flutter analyze` -> pass +- `flutter test` -> pass + +## Known Gaps / Intentional Scope Limits + +- No realtime transport yet (SSE/WebSocket intentionally deferred). +- No backend-generated client code from OpenAPI yet. +- No persistence layer wiring to local DB yet (gateway is ready to plug in). +- No UI features using backend gateway yet beyond app shell readiness. +- Fake gateway data is deterministic sample content, not scenario-configurable yet. + +## Handoff Plan For Next Agent Sessions + +### Priority 1: Repository Hygiene + CI + +1. Add CI workflow for `flutter analyze` + `flutter test`. +2. Add branch protections / PR template if desired. +3. Keep generated files committed policy explicit in README. + +### Priority 2: Local DB Integration (Offline-First Core) + +1. Define local domain entities and repositories for `person`, `capture`, `giftIdea`, `eventIdea`, `tag`, `reminderRule`. +2. Map local mutations to `ChangeEnvelope` and enqueue for sync. +3. Create sync orchestrator service that: + - pushes pending local mutations, + - applies pull changes with conflict policy, + - stores/advances cursor. +4. Add tests for conflict handling and cursor progression. + +### Priority 3: App Wiring + UX + +1. Wire `BackendGateway` into app services/use cases (not UI widgets directly). +2. Add session state provider (signed in / expired / signed out). +3. Add explicit offline indicators and non-blocking sync status UI. +4. Start consuming fake gateway in feature screens before live backend. + +### Priority 4: Security + Hardening + +1. Add central log redaction helper for headers/bodies. +2. Add token-expiry pre-check strategy (optional proactive refresh). +3. Evaluate secure storage behavior on web target and fallback policy. +4. Add tests for concurrent 401 refresh race conditions. + +### Priority 5: API Evolution + +1. Version strategy in OpenAPI (`/v1` already set). +2. Define backend error payload schema formally in OpenAPI. +3. Add capabilities endpoint for future realtime negotiation. +4. Add SSE/WebSocket design ADR when backend is ready. + +## How To Continue Quickly + +1. Run: `flutter pub get` +2. If models change: `dart run build_runner build --delete-conflicting-outputs` +3. Validate: `flutter analyze && flutter test` +4. Start from: + - gateway surface: `lib/integrations/backend/backend_gateway.dart` + - sync transport: `lib/integrations/backend/backend_gateway_rest.dart` + - contract: `docs/api/openapi.yaml` + +## Important Notes + +- Current default base URL is placeholder (`https://api.example.com`). +- Use fake mode for local iteration: + - `flutter run --dart-define=USE_FAKE_BACKEND=true` +- Build-time URL override: + - `flutter run --dart-define=BACKEND_BASE_URL=` diff --git a/docs/SETUP.md b/docs/SETUP.md new file mode 100644 index 0000000..14a1cd0 --- /dev/null +++ b/docs/SETUP.md @@ -0,0 +1,35 @@ +# Setup + +## Backend Base URL + +Build-time (recommended): + +```bash +flutter run --dart-define=BACKEND_BASE_URL=https://api.example.com +``` + +Runtime override (for future settings screens): + +```dart +AppConfig.overrideBackendBaseUrl('https://staging.example.com'); +``` + +## Run With Fake Gateway + +Use fake gateway for local/offline development: + +```bash +flutter run --dart-define=USE_FAKE_BACKEND=true +``` + +## Run Tests + +```bash +flutter test +``` + +## Generate Code + +```bash +dart run build_runner build --delete-conflicting-outputs +``` diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml new file mode 100644 index 0000000..6e86b61 --- /dev/null +++ b/docs/api/openapi.yaml @@ -0,0 +1,344 @@ +openapi: 3.0.3 +info: + title: Relationship Saver API + version: 0.1.0 + description: | + Backend contract for auth, sync, signals, and upload scaffolding. +servers: + - url: https://api.example.com +paths: + /v1/auth/sign-in: + post: + summary: Sign in + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SignInRequest' + responses: + '200': + description: Signed-in session + content: + application/json: + schema: + $ref: '#/components/schemas/AuthSession' + /v1/auth/refresh: + post: + summary: Refresh session + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AuthRefreshRequest' + responses: + '200': + description: Refreshed token bundle + content: + application/json: + schema: + $ref: '#/components/schemas/AuthRefreshResponse' + /v1/auth/sign-out: + post: + summary: Sign out + responses: + '204': + description: Signed out + /v1/me: + get: + summary: Current user profile + responses: + '200': + description: Profile + content: + application/json: + schema: + $ref: '#/components/schemas/UserProfile' + /v1/sync/push: + post: + summary: Push local changes + parameters: + - in: header + name: Idempotency-Key + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SyncPushRequest' + responses: + '200': + description: Push result + content: + application/json: + schema: + $ref: '#/components/schemas/SyncPushResult' + /v1/sync/pull: + get: + summary: Pull remote changes + parameters: + - in: query + name: cursor + schema: + type: string + - in: query + name: limit + schema: + type: integer + default: 200 + responses: + '200': + description: Pull result + content: + application/json: + schema: + $ref: '#/components/schemas/SyncPullResult' + /v1/signals: + get: + summary: Fetch signals feed + parameters: + - in: query + name: cursor + schema: + type: string + - in: query + name: since + schema: + type: string + format: date-time + - in: query + name: limit + schema: + type: integer + default: 50 + - in: query + name: personId + schema: + type: array + items: + type: string + responses: + '200': + description: Signals page + content: + application/json: + schema: + $ref: '#/components/schemas/SignalsFeed' + /v1/signals/{id}/ack: + post: + summary: Acknowledge signal + parameters: + - in: path + name: id + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SignalAckRequest' + responses: + '204': + description: Acknowledged + /v1/uploads: + post: + summary: Initialize upload + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UploadInitRequest' + responses: + '200': + description: Upload URL + content: + application/json: + schema: + $ref: '#/components/schemas/UploadInitResult' + /v1/files/{fileId}: + get: + summary: Resolve file download URL + parameters: + - in: path + name: fileId + required: true + schema: + type: string + responses: + '200': + description: Download URL + content: + application/json: + schema: + $ref: '#/components/schemas/DownloadUrl' +components: + schemas: + SignInRequest: + type: object + properties: + method: + type: string + enum: [email_magic_link, password, oidc] + email: + type: string + format: email + password: + type: string + idToken: + type: string + required: [method] + UserProfile: + type: object + properties: + id: { type: string } + email: { type: string, format: email } + displayName: { type: string } + avatarFileId: { type: string } + required: [id] + AuthSession: + type: object + properties: + accessToken: { type: string } + refreshToken: { type: string } + expiresAt: { type: string, format: date-time } + user: + $ref: '#/components/schemas/UserProfile' + required: [accessToken, refreshToken, expiresAt, user] + AuthRefreshRequest: + type: object + properties: + refreshToken: { type: string } + required: [refreshToken] + AuthRefreshResponse: + type: object + properties: + accessToken: { type: string } + refreshToken: { type: string } + expiresAt: { type: string, format: date-time } + required: [accessToken, expiresAt] + ChangeEnvelope: + type: object + properties: + schemaVersion: { type: integer } + entityType: { type: string } + entityId: { type: string } + op: + type: string + enum: [upsert, delete] + modifiedAt: + type: string + format: date-time + clientMutationId: { type: string } + payload: + type: object + nullable: true + additionalProperties: true + required: + [schemaVersion, entityType, entityId, op, modifiedAt, clientMutationId] + SyncPushRequest: + type: object + properties: + deviceId: { type: string } + cursor: { type: string } + changes: + type: array + items: + $ref: '#/components/schemas/ChangeEnvelope' + idempotencyKey: { type: string } + required: [deviceId, changes, idempotencyKey] + MutationAck: + type: object + properties: + clientMutationId: { type: string } + serverMutationId: { type: string } + acceptedAt: { type: string, format: date-time } + required: [clientMutationId] + MutationRejection: + type: object + properties: + clientMutationId: { type: string } + code: { type: string } + message: { type: string } + required: [clientMutationId, code, message] + SyncPushResult: + type: object + properties: + cursor: { type: string } + accepted: + type: array + items: + $ref: '#/components/schemas/MutationAck' + rejected: + type: array + items: + $ref: '#/components/schemas/MutationRejection' + required: [cursor, accepted, rejected] + SyncPullResult: + type: object + properties: + cursor: { type: string } + changes: + type: array + items: + $ref: '#/components/schemas/ChangeEnvelope' + required: [cursor, changes] + SignalItem: + type: object + properties: + id: { type: string } + type: { type: string } + title: { type: string } + description: { type: string } + personId: { type: string } + createdAt: { type: string, format: date-time } + metadata: + type: object + additionalProperties: true + required: [id, type, title, createdAt] + SignalsFeed: + type: object + properties: + cursor: { type: string } + items: + type: array + items: + $ref: '#/components/schemas/SignalItem' + required: [items] + SignalAckRequest: + type: object + properties: + action: + type: string + enum: [viewed, dismissed, saved, purchased] + at: + type: string + format: date-time + required: [action, at] + UploadInitRequest: + type: object + properties: + purpose: + type: string + enum: [avatar, attachment] + contentType: { type: string } + sizeBytes: { type: integer } + required: [purpose, contentType, sizeBytes] + UploadInitResult: + type: object + properties: + uploadUrl: { type: string } + fileId: { type: string } + expiresAt: { type: string, format: date-time } + required: [uploadUrl, fileId, expiresAt] + DownloadUrl: + type: object + properties: + downloadUrl: { type: string } + expiresAt: { type: string, format: date-time } + required: [downloadUrl, expiresAt] diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..beb9f5f --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,623 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Y98YFL3B2W; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Y98YFL3B2W; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = Y98YFL3B2W; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..4fb982a --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,70 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Relationship Saver + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + relationship_saver + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..b9ce8ea --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,6 @@ +import Flutter +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + +} diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/core/auth/in_memory_token_store.dart b/lib/core/auth/in_memory_token_store.dart new file mode 100644 index 0000000..102ac95 --- /dev/null +++ b/lib/core/auth/in_memory_token_store.dart @@ -0,0 +1,20 @@ +import 'package:relationship_saver/core/auth/token_store.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +/// Simple in-memory [TokenStore] used in tests and local fakes. +class InMemoryTokenStore implements TokenStore { + AuthSession? _session; + + @override + Future clear() async { + _session = null; + } + + @override + Future read() async => _session; + + @override + Future write(AuthSession session) async { + _session = session; + } +} diff --git a/lib/core/auth/secure_token_store.dart b/lib/core/auth/secure_token_store.dart new file mode 100644 index 0000000..10054ac --- /dev/null +++ b/lib/core/auth/secure_token_store.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:relationship_saver/core/auth/token_store.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +/// [TokenStore] backed by platform secure storage. +class SecureTokenStore implements TokenStore { + SecureTokenStore({FlutterSecureStorage? storage}) + : _storage = storage ?? const FlutterSecureStorage(); + + static const String _sessionKey = 'backend_auth_session_v1'; + final FlutterSecureStorage _storage; + + @override + Future read() async { + final String? raw = await _storage.read(key: _sessionKey); + if (raw == null || raw.isEmpty) { + return null; + } + + try { + final Map json = jsonDecode(raw) as Map; + return AuthSession.fromJson(json); + } on FormatException { + await clear(); + return null; + } + } + + @override + Future write(AuthSession session) async { + final String raw = jsonEncode(session.toJson()); + await _storage.write(key: _sessionKey, value: raw); + } + + @override + Future clear() => _storage.delete(key: _sessionKey); +} diff --git a/lib/core/auth/token_store.dart b/lib/core/auth/token_store.dart new file mode 100644 index 0000000..b3fec88 --- /dev/null +++ b/lib/core/auth/token_store.dart @@ -0,0 +1,13 @@ +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +/// Session token persistence abstraction. +abstract interface class TokenStore { + /// Reads the current session, if any. + Future read(); + + /// Persists a full auth session. + Future write(AuthSession session); + + /// Clears all stored auth state. + Future clear(); +} diff --git a/lib/core/config/app_config.dart b/lib/core/config/app_config.dart new file mode 100644 index 0000000..80caec0 --- /dev/null +++ b/lib/core/config/app_config.dart @@ -0,0 +1,29 @@ +/// Application-level runtime configuration. +class AppConfig { + AppConfig._(); + + static const String _defaultBaseUrl = String.fromEnvironment( + 'BACKEND_BASE_URL', + defaultValue: 'https://api.example.com', + ); + + static String? _baseUrlOverride; + + /// Returns the configured backend base URL. + static String get backendBaseUrl { + final String? override = _baseUrlOverride; + if (override != null && override.trim().isNotEmpty) { + return override.trim(); + } + return _defaultBaseUrl; + } + + /// Enables fake backend implementation for local/offline development. + static bool get useFakeBackend => + const bool.fromEnvironment('USE_FAKE_BACKEND', defaultValue: false); + + /// Runtime override for backend URL (e.g. local settings screen). + static void overrideBackendBaseUrl(String? baseUrl) { + _baseUrlOverride = baseUrl; + } +} diff --git a/lib/core/config/app_theme.dart b/lib/core/config/app_theme.dart new file mode 100644 index 0000000..f4244de --- /dev/null +++ b/lib/core/config/app_theme.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +/// Shared app theme, aligned with the visual tone of the reference templates. +class AppTheme { + AppTheme._(); + + static const Color background = Color(0xFFEDF0F2); + static const Color surface = Color(0xFFFFFFFF); + static const Color primary = Color(0xFF253840); + static const Color accent = Color(0xFF00B6F0); + + /// Builds the Material [ThemeData]. + static ThemeData light() { + final ColorScheme colorScheme = ColorScheme.fromSeed( + seedColor: primary, + primary: primary, + secondary: accent, + surface: surface, + brightness: Brightness.light, + ); + + return ThemeData( + useMaterial3: true, + colorScheme: colorScheme, + scaffoldBackgroundColor: background, + appBarTheme: const AppBarTheme( + centerTitle: false, + backgroundColor: surface, + foregroundColor: primary, + elevation: 0, + ), + ); + } +} diff --git a/lib/core/network/backend_exception.dart b/lib/core/network/backend_exception.dart new file mode 100644 index 0000000..c056349 --- /dev/null +++ b/lib/core/network/backend_exception.dart @@ -0,0 +1,127 @@ +/// Base exception type for backend-facing failures. +sealed class BackendException implements Exception { + const BackendException( + this.message, { + this.statusCode, + this.requestId, + this.backendCode, + }); + + /// Human-readable message suitable for diagnostics. + final String message; + + /// HTTP status code if available. + final int? statusCode; + + /// Correlation/request identifier emitted by backend or gateway. + final String? requestId; + + /// Structured backend error code if provided by server. + final String? backendCode; + + @override + String toString() { + return 'BackendException(message: $message, statusCode: $statusCode, ' + 'requestId: $requestId, backendCode: $backendCode)'; + } +} + +/// No HTTP response was received due to connectivity issues. +final class NetworkException extends BackendException { + const NetworkException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Request timed out at connect/send/receive stage. +final class TimeoutException extends BackendException { + const TimeoutException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Backend rejected request due to missing/invalid auth. +class UnauthorizedException extends BackendException { + const UnauthorizedException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Session is no longer valid and refresh failed. +final class AuthExpiredException extends UnauthorizedException { + const AuthExpiredException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Caller is authenticated but lacks permissions. +final class ForbiddenException extends BackendException { + const ForbiddenException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Resource was not found. +final class NotFoundException extends BackendException { + const NotFoundException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Backend rate-limited the request. +final class RateLimitedException extends BackendException { + const RateLimitedException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// 5xx backend failure. +final class ServerException extends BackendException { + const ServerException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Request was syntactically valid but semantically invalid. +final class ValidationException extends BackendException { + const ValidationException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} + +/// Fallback for failures that do not map to a known category. +final class UnknownBackendException extends BackendException { + const UnknownBackendException( + super.message, { + super.statusCode, + super.requestId, + super.backendCode, + }); +} diff --git a/lib/core/network/dio_error_mapper.dart b/lib/core/network/dio_error_mapper.dart new file mode 100644 index 0000000..1fbfac8 --- /dev/null +++ b/lib/core/network/dio_error_mapper.dart @@ -0,0 +1,171 @@ +import 'package:dio/dio.dart'; + +import 'package:relationship_saver/core/network/backend_exception.dart'; +import 'package:relationship_saver/core/network/network_constants.dart'; + +/// Maps Dio transport errors into typed [BackendException] failures. +BackendException mapDioException(DioException error) { + final Object? embeddedError = error.error; + if (embeddedError is BackendException) { + return embeddedError; + } + + final Response? response = error.response; + final int? statusCode = response?.statusCode; + final Map? responseMap = _asMapOrNull(response?.data); + final String? requestId = _extractRequestId(response, responseMap); + final String? backendCode = _extractBackendCode(responseMap); + final String message = _extractMessage(responseMap, error.message); + + switch (error.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + return TimeoutException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + case DioExceptionType.connectionError: + return NetworkException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + case DioExceptionType.badResponse: + if (statusCode == null) { + return UnknownBackendException( + message, + requestId: requestId, + backendCode: backendCode, + ); + } + if (statusCode == 400 || statusCode == 422) { + return ValidationException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + } + if (statusCode == 401) { + return UnauthorizedException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + } + if (statusCode == 403) { + return ForbiddenException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + } + if (statusCode == 404) { + return NotFoundException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + } + if (statusCode == 429) { + return RateLimitedException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + } + if (statusCode >= 500 && statusCode < 600) { + return ServerException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + } + return UnknownBackendException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + case DioExceptionType.cancel: + case DioExceptionType.badCertificate: + case DioExceptionType.unknown: + return UnknownBackendException( + message, + statusCode: statusCode, + requestId: requestId, + backendCode: backendCode, + ); + } +} + +Map? _asMapOrNull(Object? value) { + if (value is Map) { + return value; + } + if (value is Map) { + return value.map( + (Object? key, Object? val) => MapEntry(key.toString(), val), + ); + } + return null; +} + +String? _extractRequestId( + Response? response, + Map? responseMap, +) { + final List? fromHeader = + response?.headers.map[NetworkConstants.requestIdHeader.toLowerCase()] ?? + response?.headers.map[NetworkConstants.requestIdHeader]; + if (fromHeader != null && fromHeader.isNotEmpty) { + return fromHeader.first; + } + + final Object? requestId = + responseMap?['requestId'] ?? responseMap?['request_id']; + if (requestId is String && requestId.trim().isNotEmpty) { + return requestId; + } + return null; +} + +String? _extractBackendCode(Map? responseMap) { + final Object? directCode = responseMap?['code']; + if (directCode is String) { + return directCode; + } + + final Map? error = _asMapOrNull(responseMap?['error']); + final Object? nestedCode = error?['code']; + if (nestedCode is String) { + return nestedCode; + } + return null; +} + +String _extractMessage(Map? responseMap, String? fallback) { + final Object? direct = responseMap?['message']; + if (direct is String && direct.trim().isNotEmpty) { + return direct; + } + + final Map? error = _asMapOrNull(responseMap?['error']); + final Object? nested = error?['message']; + if (nested is String && nested.trim().isNotEmpty) { + return nested; + } + + return fallback?.trim().isNotEmpty == true + ? fallback!.trim() + : 'Backend request failed'; +} diff --git a/lib/core/network/dio_factory.dart b/lib/core/network/dio_factory.dart new file mode 100644 index 0000000..3d2159f --- /dev/null +++ b/lib/core/network/dio_factory.dart @@ -0,0 +1,55 @@ +import 'package:clock/clock.dart'; +import 'package:dio/dio.dart'; +import 'package:relationship_saver/core/auth/token_store.dart'; +import 'package:relationship_saver/core/network/interceptors/auth_interceptor.dart'; +import 'package:relationship_saver/core/network/interceptors/refresh_token_interceptor.dart'; +import 'package:relationship_saver/core/network/interceptors/request_metadata_interceptor.dart'; +import 'package:relationship_saver/core/network/interceptors/retry_interceptor.dart'; +import 'package:uuid/uuid.dart'; + +/// Builds configured Dio clients for backend integration. +class DioFactory { + DioFactory._(); + + /// Creates Dio client with auth, retry, and refresh handling. + static Dio create({ + required String baseUrl, + required TokenStore tokenStore, + Uuid? uuid, + Clock? clock, + HttpClientAdapter? httpClientAdapter, + }) { + final BaseOptions options = BaseOptions( + baseUrl: baseUrl, + connectTimeout: const Duration(seconds: 10), + sendTimeout: const Duration(seconds: 10), + receiveTimeout: const Duration(seconds: 15), + headers: const { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + ); + + final Dio dio = Dio(options); + final Dio refreshDio = Dio(options); + + if (httpClientAdapter != null) { + dio.httpClientAdapter = httpClientAdapter; + refreshDio.httpClientAdapter = httpClientAdapter; + } + + dio.interceptors.addAll([ + RequestMetadataInterceptor(uuid: uuid), + AuthInterceptor(tokenStore: tokenStore), + RefreshTokenInterceptor( + dio: dio, + refreshDio: refreshDio, + tokenStore: tokenStore, + clock: clock, + ), + RetryInterceptor(dio: dio, clock: clock), + ]); + + return dio; + } +} diff --git a/lib/core/network/interceptors/auth_interceptor.dart b/lib/core/network/interceptors/auth_interceptor.dart new file mode 100644 index 0000000..45f42c4 --- /dev/null +++ b/lib/core/network/interceptors/auth_interceptor.dart @@ -0,0 +1,30 @@ +import 'package:dio/dio.dart'; +import 'package:relationship_saver/core/auth/token_store.dart'; +import 'package:relationship_saver/core/network/network_constants.dart'; + +/// Adds bearer access token to outgoing requests. +class AuthInterceptor extends Interceptor { + AuthInterceptor({required TokenStore tokenStore}) : _tokenStore = tokenStore; + + final TokenStore _tokenStore; + + @override + Future onRequest( + RequestOptions options, + RequestInterceptorHandler handler, + ) async { + if (options.extra[NetworkConstants.extraSkipAuth] == true) { + handler.next(options); + return; + } + + final session = await _tokenStore.read(); + if (session?.accessToken case final String accessToken + when accessToken.isNotEmpty) { + options.headers[NetworkConstants.authorizationHeader] = + 'Bearer $accessToken'; + } + + handler.next(options); + } +} diff --git a/lib/core/network/interceptors/refresh_token_interceptor.dart b/lib/core/network/interceptors/refresh_token_interceptor.dart new file mode 100644 index 0000000..2d7703b --- /dev/null +++ b/lib/core/network/interceptors/refresh_token_interceptor.dart @@ -0,0 +1,154 @@ +import 'package:clock/clock.dart'; +import 'package:dio/dio.dart'; +import 'package:relationship_saver/core/auth/token_store.dart'; +import 'package:relationship_saver/core/network/backend_exception.dart'; +import 'package:relationship_saver/core/network/network_constants.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +/// Handles access-token refresh on 401 responses and retries the original call. +class RefreshTokenInterceptor extends Interceptor { + RefreshTokenInterceptor({ + required Dio dio, + required Dio refreshDio, + required TokenStore tokenStore, + Clock? clock, + }) : _dio = dio, + _refreshDio = refreshDio, + _tokenStore = tokenStore, + _clock = clock ?? const Clock(); + + final Dio _dio; + final Dio _refreshDio; + final TokenStore _tokenStore; + final Clock _clock; + Future? _ongoingRefresh; + + @override + Future onError( + DioException err, + ErrorInterceptorHandler handler, + ) async { + if (!_shouldHandle(err.requestOptions, err.response?.statusCode)) { + handler.next(err); + return; + } + + try { + final AuthRefreshResponse? refreshed = await _refreshOnce(); + if (refreshed == null) { + await _tokenStore.clear(); + handler.reject(_asAuthExpired(err)); + return; + } + + final RequestOptions retried = err.requestOptions.copyWith( + headers: { + ...err.requestOptions.headers, + NetworkConstants.authorizationHeader: + 'Bearer ${refreshed.accessToken}', + }, + extra: { + ...err.requestOptions.extra, + NetworkConstants.extraDidRefresh: true, + }, + ); + + final Response response = await _dio.fetch(retried); + handler.resolve(response); + } on DioException catch (refreshFailure) { + await _tokenStore.clear(); + handler.reject( + DioException( + requestOptions: err.requestOptions, + response: refreshFailure.response, + error: const AuthExpiredException('Authentication expired'), + type: DioExceptionType.badResponse, + ), + ); + } catch (_) { + await _tokenStore.clear(); + handler.reject(_asAuthExpired(err)); + } + } + + bool _shouldHandle(RequestOptions options, int? statusCode) { + if (statusCode != 401) { + return false; + } + if (options.extra[NetworkConstants.extraSkipRefresh] == true || + options.extra[NetworkConstants.extraDidRefresh] == true) { + return false; + } + return !options.path.endsWith('/v1/auth/refresh'); + } + + Future _refreshOnce() async { + final Future? inflight = _ongoingRefresh; + if (inflight != null) { + return inflight; + } + + final Future future = _refreshInternal(); + _ongoingRefresh = future; + try { + return await future; + } finally { + _ongoingRefresh = null; + } + } + + Future _refreshInternal() async { + final AuthSession? current = await _tokenStore.read(); + if (current == null || current.refreshToken.isEmpty) { + return null; + } + + final Response response = await _refreshDio.post( + '/v1/auth/refresh', + data: AuthRefreshRequest(refreshToken: current.refreshToken).toJson(), + options: Options( + headers: { + NetworkConstants.requestIdHeader: + 'refresh-${_clock.now().microsecondsSinceEpoch}', + }, + extra: { + NetworkConstants.extraSkipAuth: true, + NetworkConstants.extraSkipRefresh: true, + }, + ), + ); + + final AuthRefreshResponse refreshed = AuthRefreshResponse.fromJson( + _asJsonMap(response.data), + ); + final AuthSession updated = current.copyWith( + accessToken: refreshed.accessToken, + refreshToken: refreshed.refreshToken ?? current.refreshToken, + expiresAt: refreshed.expiresAt, + ); + + await _tokenStore.write(updated); + return refreshed; + } + + Map _asJsonMap(dynamic value) { + if (value is Map) { + return value; + } + if (value is Map) { + return value.map( + (Object? key, Object? val) => MapEntry(key.toString(), val), + ); + } + throw const FormatException('Expected JSON object'); + } + + DioException _asAuthExpired(DioException source) { + return DioException( + requestOptions: source.requestOptions, + response: source.response, + type: DioExceptionType.badResponse, + error: const AuthExpiredException('Authentication expired'), + ); + } +} diff --git a/lib/core/network/interceptors/request_metadata_interceptor.dart b/lib/core/network/interceptors/request_metadata_interceptor.dart new file mode 100644 index 0000000..5141841 --- /dev/null +++ b/lib/core/network/interceptors/request_metadata_interceptor.dart @@ -0,0 +1,19 @@ +import 'package:dio/dio.dart'; +import 'package:relationship_saver/core/network/network_constants.dart'; +import 'package:uuid/uuid.dart'; + +/// Adds per-request metadata headers such as request ID. +class RequestMetadataInterceptor extends Interceptor { + RequestMetadataInterceptor({Uuid? uuid}) : _uuid = uuid ?? const Uuid(); + + final Uuid _uuid; + + @override + void onRequest(RequestOptions options, RequestInterceptorHandler handler) { + options.headers.putIfAbsent( + NetworkConstants.requestIdHeader, + () => _uuid.v4(), + ); + handler.next(options); + } +} diff --git a/lib/core/network/interceptors/retry_interceptor.dart b/lib/core/network/interceptors/retry_interceptor.dart new file mode 100644 index 0000000..053117a --- /dev/null +++ b/lib/core/network/interceptors/retry_interceptor.dart @@ -0,0 +1,91 @@ +import 'dart:math' as math; + +import 'package:clock/clock.dart'; +import 'package:dio/dio.dart'; +import 'package:relationship_saver/core/network/network_constants.dart'; + +/// Retries transient failures with exponential backoff. +class RetryInterceptor extends Interceptor { + RetryInterceptor({ + required Dio dio, + this.maxRetries = 2, + this.baseDelay = const Duration(milliseconds: 200), + Clock? clock, + }) : _dio = dio, + _clock = clock ?? const Clock(); + + final Dio _dio; + final int maxRetries; + final Duration baseDelay; + final Clock _clock; + + @override + Future onError( + DioException err, + ErrorInterceptorHandler handler, + ) async { + final RequestOptions request = err.requestOptions; + final int attempt = + (request.extra[NetworkConstants.extraRetryAttempt] as int?) ?? 0; + + if (!_shouldRetry(err, request) || attempt >= maxRetries) { + handler.next(err); + return; + } + + final int multiplier = math.pow(2, attempt).toInt(); + final Duration delay = Duration( + milliseconds: baseDelay.inMilliseconds * multiplier, + ); + + final DateTime wakeAt = _clock.now().add(delay); + final Duration sleepFor = wakeAt.difference(_clock.now()); + await Future.delayed(sleepFor.isNegative ? Duration.zero : sleepFor); + + final RequestOptions retried = request.copyWith( + extra: { + ...request.extra, + NetworkConstants.extraRetryAttempt: attempt + 1, + }, + ); + + try { + final Response response = await _dio.fetch(retried); + handler.resolve(response); + } on DioException catch (retryError) { + handler.next(retryError); + } + } + + bool _shouldRetry(DioException error, RequestOptions request) { + if (!_isTransient(error)) { + return false; + } + + final String method = request.method.toUpperCase(); + if (method == 'GET' || method == 'HEAD' || method == 'OPTIONS') { + return true; + } + + final Object? idempotency = + request.headers[NetworkConstants.idempotencyKeyHeader]; + return idempotency is String && idempotency.trim().isNotEmpty; + } + + bool _isTransient(DioException error) { + switch (error.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + case DioExceptionType.connectionError: + return true; + case DioExceptionType.badResponse: + final int? status = error.response?.statusCode; + return status != null && status >= 500 && status < 600; + case DioExceptionType.badCertificate: + case DioExceptionType.cancel: + case DioExceptionType.unknown: + return false; + } + } +} diff --git a/lib/core/network/network_constants.dart b/lib/core/network/network_constants.dart new file mode 100644 index 0000000..3b0ab63 --- /dev/null +++ b/lib/core/network/network_constants.dart @@ -0,0 +1,13 @@ +/// HTTP header keys and Dio `extra` keys used by network interceptors. +class NetworkConstants { + NetworkConstants._(); + + static const String authorizationHeader = 'Authorization'; + static const String requestIdHeader = 'X-Request-Id'; + static const String idempotencyKeyHeader = 'Idempotency-Key'; + + static const String extraSkipAuth = 'skipAuth'; + static const String extraSkipRefresh = 'skipRefresh'; + static const String extraDidRefresh = 'didRefresh'; + static const String extraRetryAttempt = 'retryAttempt'; +} diff --git a/lib/core/utils/time_provider.dart b/lib/core/utils/time_provider.dart new file mode 100644 index 0000000..abfe3a5 --- /dev/null +++ b/lib/core/utils/time_provider.dart @@ -0,0 +1,9 @@ +import 'package:clock/clock.dart'; + +/// Shared clock helper for deterministic testing. +class TimeProvider { + TimeProvider._(); + + /// Current wall-clock time. + static DateTime now() => clock.now(); +} diff --git a/lib/integrations/backend/backend_gateway.dart b/lib/integrations/backend/backend_gateway.dart new file mode 100644 index 0000000..3ac0390 --- /dev/null +++ b/lib/integrations/backend/backend_gateway.dart @@ -0,0 +1,47 @@ +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +/// Domain-level backend boundary independent of HTTP transport. +abstract interface class BackendGateway { + /// Authenticates user and returns active session. + Future signIn(SignInRequest request); + + /// Signs out current user. + Future signOut(); + + /// Returns current user profile. + Future me(); + + /// Pulls remote changes since optional cursor. + Future pullChanges({String? cursor, int limit = 200}); + + /// Pushes local changes to backend. + Future pushChanges({ + required List changes, + String? cursor, + }); + + /// Fetches recommendation signals feed. + Future getSignalsFeed({ + String? cursor, + DateTime? since, + int limit = 50, + Set? personIds, + }); + + /// Acknowledges a signal action. + Future acknowledgeSignal({ + required String signalId, + required SignalAction action, + DateTime? at, + }); + + /// Initializes upload metadata and pre-signed URL. + Future initUpload({ + required UploadPurpose purpose, + required String contentType, + required int sizeBytes, + }); + + /// Resolves a temporary download URL. + Future getDownloadUrl({required String fileId}); +} diff --git a/lib/integrations/backend/backend_gateway_fake.dart b/lib/integrations/backend/backend_gateway_fake.dart new file mode 100644 index 0000000..aca0825 --- /dev/null +++ b/lib/integrations/backend/backend_gateway_fake.dart @@ -0,0 +1,160 @@ +import 'package:clock/clock.dart'; +import 'package:relationship_saver/integrations/backend/backend_gateway.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; +import 'package:uuid/uuid.dart'; + +/// Deterministic fake backend for local development and tests. +class BackendGatewayFake implements BackendGateway { + BackendGatewayFake({Clock? clock, Uuid? uuid}) + : _clock = clock ?? const Clock(), + _uuid = uuid ?? const Uuid(); + + final Clock _clock; + final Uuid _uuid; + + static final DateTime _seedTime = DateTime.utc(2026, 1, 1, 8); + + @override + Future acknowledgeSignal({ + required String signalId, + required SignalAction action, + DateTime? at, + }) async { + return; + } + + @override + Future getDownloadUrl({required String fileId}) async { + return DownloadUrl( + downloadUrl: 'https://files.example.com/download/$fileId', + expiresAt: _seedTime.add(const Duration(hours: 1)), + ); + } + + @override + Future getSignalsFeed({ + String? cursor, + DateTime? since, + int limit = 50, + Set? personIds, + }) async { + final List seeded = [ + SignalItem( + id: 'sig-1', + type: 'deal', + title: 'Weekend coffee promo nearby', + description: 'Save this for your next date idea.', + personId: personIds?.isNotEmpty == true ? personIds!.first : 'person-1', + createdAt: _seedTime, + metadata: const {'merchant': 'Cafe Bloom'}, + ), + SignalItem( + id: 'sig-2', + type: 'trend', + title: 'Books are trending in gift ideas', + description: 'Reading-themed gifts up 24% this week.', + personId: personIds?.isNotEmpty == true ? personIds!.first : 'person-1', + createdAt: _seedTime.add(const Duration(hours: 3)), + ), + SignalItem( + id: 'sig-3', + type: 'recommendation', + title: 'Try a sunset walk reminder', + description: 'Low-effort ritual with high emotional impact.', + personId: personIds?.isNotEmpty == true ? personIds!.first : 'person-2', + createdAt: _seedTime.add(const Duration(hours: 6)), + ), + ]; + + return SignalsFeed( + cursor: 'fake-cursor-1', + items: seeded.take(limit).toList(), + ); + } + + @override + Future initUpload({ + required UploadPurpose purpose, + required String contentType, + required int sizeBytes, + }) async { + final String fileId = 'file-${_uuid.v4()}'; + return UploadInitResult( + uploadUrl: 'https://uploads.example.com/$fileId', + fileId: fileId, + expiresAt: _seedTime.add(const Duration(minutes: 15)), + ); + } + + @override + Future me() async { + return const UserProfile( + id: 'user-fake', + email: 'demo@example.com', + displayName: 'Demo User', + ); + } + + @override + Future pullChanges({String? cursor, int limit = 200}) async { + final List changes = [ + ChangeEnvelope( + schemaVersion: 1, + entityType: 'person', + entityId: 'person-1', + op: ChangeOperation.upsert, + modifiedAt: _seedTime, + clientMutationId: 'cm-1', + payload: const {'name': 'Alex', 'importance': 5}, + ), + ]; + + return SyncPullResult( + cursor: 'fake-pull-cursor-1', + changes: changes.take(limit).toList(), + ); + } + + @override + Future pushChanges({ + required List changes, + String? cursor, + }) async { + final DateTime now = _clock.now().toUtc(); + final List accepted = changes + .map( + (ChangeEnvelope change) => MutationAck( + clientMutationId: change.clientMutationId, + serverMutationId: 'srv-${change.clientMutationId}', + acceptedAt: now, + ), + ) + .toList(growable: false); + + return SyncPushResult( + cursor: 'fake-push-cursor-1', + accepted: accepted, + rejected: const [], + ); + } + + @override + Future signIn(SignInRequest request) async { + final DateTime now = _clock.now().toUtc(); + return AuthSession( + accessToken: 'fake-access-token', + refreshToken: 'fake-refresh-token', + expiresAt: now.add(const Duration(hours: 1)), + user: UserProfile( + id: 'user-fake', + email: request.email ?? 'demo@example.com', + displayName: 'Demo User', + ), + ); + } + + @override + Future signOut() async { + return; + } +} diff --git a/lib/integrations/backend/backend_gateway_provider.dart b/lib/integrations/backend/backend_gateway_provider.dart new file mode 100644 index 0000000..4b91dc7 --- /dev/null +++ b/lib/integrations/backend/backend_gateway_provider.dart @@ -0,0 +1,36 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:relationship_saver/core/auth/secure_token_store.dart'; +import 'package:relationship_saver/core/auth/token_store.dart'; +import 'package:relationship_saver/core/config/app_config.dart'; +import 'package:relationship_saver/core/network/dio_factory.dart'; +import 'package:relationship_saver/integrations/backend/backend_gateway.dart'; +import 'package:relationship_saver/integrations/backend/backend_gateway_fake.dart'; +import 'package:relationship_saver/integrations/backend/backend_gateway_rest.dart'; + +/// Provides token storage implementation. +final Provider tokenStoreProvider = Provider((Ref ref) { + return SecureTokenStore(); +}); + +/// Provides configured Dio client for backend transport. +final Provider backendDioProvider = Provider((Ref ref) { + final TokenStore tokenStore = ref.watch(tokenStoreProvider); + return DioFactory.create( + baseUrl: AppConfig.backendBaseUrl, + tokenStore: tokenStore, + ); +}); + +/// Provides backend gateway implementation. +final Provider backendGatewayProvider = + Provider((Ref ref) { + if (AppConfig.useFakeBackend) { + return BackendGatewayFake(); + } + + return BackendGatewayRest( + dio: ref.watch(backendDioProvider), + tokenStore: ref.watch(tokenStoreProvider), + ); + }); diff --git a/lib/integrations/backend/backend_gateway_rest.dart b/lib/integrations/backend/backend_gateway_rest.dart new file mode 100644 index 0000000..4693ea1 --- /dev/null +++ b/lib/integrations/backend/backend_gateway_rest.dart @@ -0,0 +1,213 @@ +import 'package:clock/clock.dart'; +import 'package:dio/dio.dart'; +import 'package:relationship_saver/core/auth/token_store.dart'; +import 'package:relationship_saver/core/network/dio_error_mapper.dart'; +import 'package:relationship_saver/core/network/network_constants.dart'; +import 'package:relationship_saver/integrations/backend/backend_gateway.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; +import 'package:relationship_saver/integrations/backend/sync_envelope_validator.dart'; +import 'package:uuid/uuid.dart'; + +/// REST implementation of [BackendGateway] backed by Dio. +class BackendGatewayRest implements BackendGateway { + BackendGatewayRest({ + required Dio dio, + required TokenStore tokenStore, + Uuid? uuid, + Clock? clock, + this.deviceId = 'device-local', + }) : _dio = dio, + _tokenStore = tokenStore, + _uuid = uuid ?? const Uuid(), + _clock = clock ?? const Clock(); + + final Dio _dio; + final TokenStore _tokenStore; + final Uuid _uuid; + final Clock _clock; + final String deviceId; + + @override + Future acknowledgeSignal({ + required String signalId, + required SignalAction action, + DateTime? at, + }) { + return _guard(() async { + final SignalAckRequest body = SignalAckRequest( + action: action, + at: at ?? _clock.now(), + ); + + await _dio.post( + '/v1/signals/$signalId/ack', + data: body.toJson(), + ); + }); + } + + @override + Future getDownloadUrl({required String fileId}) { + return _guard(() async { + final Response response = await _dio.get( + '/v1/files/$fileId', + ); + return DownloadUrl.fromJson(_asJsonMap(response.data)); + }); + } + + @override + Future getSignalsFeed({ + String? cursor, + DateTime? since, + int limit = 50, + Set? personIds, + }) { + return _guard(() async { + final Map query = {'limit': limit}; + if (cursor != null && cursor.isNotEmpty) { + query['cursor'] = cursor; + } + if (since != null) { + query['since'] = since.toUtc().toIso8601String(); + } + if (personIds != null && personIds.isNotEmpty) { + query['personId'] = personIds.toList(growable: false); + } + + final Response response = await _dio.get( + '/v1/signals', + queryParameters: query, + ); + return SignalsFeed.fromJson(_asJsonMap(response.data)); + }); + } + + @override + Future initUpload({ + required UploadPurpose purpose, + required String contentType, + required int sizeBytes, + }) { + return _guard(() async { + final UploadInitRequest request = UploadInitRequest( + purpose: purpose, + contentType: contentType, + sizeBytes: sizeBytes, + ); + final Response response = await _dio.post( + '/v1/uploads', + data: request.toJson(), + ); + return UploadInitResult.fromJson(_asJsonMap(response.data)); + }); + } + + @override + Future me() { + return _guard(() async { + final Response response = await _dio.get('/v1/me'); + return UserProfile.fromJson(_asJsonMap(response.data)); + }); + } + + @override + Future pullChanges({String? cursor, int limit = 200}) { + return _guard(() async { + final Map query = {'limit': limit}; + if (cursor != null && cursor.isNotEmpty) { + query['cursor'] = cursor; + } + + final Response response = await _dio.get( + '/v1/sync/pull', + queryParameters: query, + ); + return SyncPullResult.fromJson(_asJsonMap(response.data)); + }); + } + + @override + Future pushChanges({ + required List changes, + String? cursor, + }) { + return _guard(() async { + SyncEnvelopeValidator.validateAll(changes); + + final String idempotencyKey = _uuid.v4(); + final SyncPushRequest request = SyncPushRequest( + deviceId: deviceId, + cursor: cursor, + changes: changes, + idempotencyKey: idempotencyKey, + ); + + final Response response = await _dio.post( + '/v1/sync/push', + data: request.toJson(), + options: Options( + headers: { + NetworkConstants.idempotencyKeyHeader: idempotencyKey, + }, + ), + ); + return SyncPushResult.fromJson(_asJsonMap(response.data)); + }); + } + + @override + Future signIn(SignInRequest request) { + return _guard(() async { + final Response response = await _dio.post( + '/v1/auth/sign-in', + data: request.toJson(), + options: Options( + extra: const { + NetworkConstants.extraSkipAuth: true, + NetworkConstants.extraSkipRefresh: true, + }, + ), + ); + + final AuthSession session = AuthSession.fromJson( + _asJsonMap(response.data), + ); + await _tokenStore.write(session); + return session; + }); + } + + @override + Future signOut() async { + try { + await _guard(() async { + await _dio.post('/v1/auth/sign-out'); + }); + } finally { + await _tokenStore.clear(); + } + } + + Future _guard(Future Function() run) async { + try { + return await run(); + } on DioException catch (error) { + throw mapDioException(error); + } + } + + Map _asJsonMap(dynamic value) { + if (value is Map) { + return value; + } + if (value is Map) { + return value.map( + (Object? key, Object? val) => MapEntry(key.toString(), val), + ); + } + throw FormatException( + 'Expected JSON object response, got ${value.runtimeType}', + ); + } +} diff --git a/lib/integrations/backend/mappers/README.md b/lib/integrations/backend/mappers/README.md new file mode 100644 index 0000000..90d054a --- /dev/null +++ b/lib/integrations/backend/mappers/README.md @@ -0,0 +1,4 @@ +# Backend Mappers + +This folder is reserved for DTO <-> domain mappers as domain aggregates are introduced. +Current MVP uses shared typed models directly to keep the gateway layer lean. diff --git a/lib/integrations/backend/models/backend_models.dart b/lib/integrations/backend/models/backend_models.dart new file mode 100644 index 0000000..cbbfed6 --- /dev/null +++ b/lib/integrations/backend/models/backend_models.dart @@ -0,0 +1,290 @@ +// ignore_for_file: invalid_annotation_target + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'backend_models.freezed.dart'; +part 'backend_models.g.dart'; + +/// Supported authentication methods. +@JsonEnum(fieldRename: FieldRename.snake) +enum SignInMethod { + /// Email link based sign-in. + emailMagicLink, + + /// Email/password sign-in. + password, + + /// OIDC token based sign-in. + oidc, +} + +/// Allowed change operation types. +@JsonEnum(alwaysCreate: true) +enum ChangeOperation { + /// Create or update entity state. + @JsonValue('upsert') + upsert, + + /// Remove entity state. + @JsonValue('delete') + delete, +} + +/// Action taken by user against a signal item. +@JsonEnum(alwaysCreate: true) +enum SignalAction { + /// Item was viewed. + viewed, + + /// Item was dismissed. + dismissed, + + /// Item was saved. + saved, + + /// Item resulted in purchase. + purchased, +} + +/// Upload category. +@JsonEnum(alwaysCreate: true) +enum UploadPurpose { + /// Avatar upload. + @JsonValue('avatar') + avatar, + + /// Generic attachment upload. + @JsonValue('attachment') + attachment, +} + +/// Request body for sign-in. +@freezed +abstract class SignInRequest with _$SignInRequest { + const factory SignInRequest({ + required SignInMethod method, + String? email, + String? password, + String? idToken, + }) = _SignInRequest; + + factory SignInRequest.fromJson(Map json) => + _$SignInRequestFromJson(json); +} + +/// User profile returned by backend. +@freezed +abstract class UserProfile with _$UserProfile { + const factory UserProfile({ + required String id, + String? email, + String? displayName, + String? avatarFileId, + }) = _UserProfile; + + factory UserProfile.fromJson(Map json) => + _$UserProfileFromJson(json); +} + +/// Auth session bundle used by gateway and token storage. +@freezed +abstract class AuthSession with _$AuthSession { + @JsonSerializable(explicitToJson: true) + const factory AuthSession({ + required String accessToken, + required String refreshToken, + required DateTime expiresAt, + required UserProfile user, + }) = _AuthSession; + + factory AuthSession.fromJson(Map json) => + _$AuthSessionFromJson(json); +} + +/// Refresh token request body. +@freezed +abstract class AuthRefreshRequest with _$AuthRefreshRequest { + const factory AuthRefreshRequest({required String refreshToken}) = + _AuthRefreshRequest; + + factory AuthRefreshRequest.fromJson(Map json) => + _$AuthRefreshRequestFromJson(json); +} + +/// Refresh token response body. +@freezed +abstract class AuthRefreshResponse with _$AuthRefreshResponse { + const factory AuthRefreshResponse({ + required String accessToken, + String? refreshToken, + required DateTime expiresAt, + }) = _AuthRefreshResponse; + + factory AuthRefreshResponse.fromJson(Map json) => + _$AuthRefreshResponseFromJson(json); +} + +/// Generic envelope for offline-first change operations. +@freezed +abstract class ChangeEnvelope with _$ChangeEnvelope { + const factory ChangeEnvelope({ + required int schemaVersion, + required String entityType, + required String entityId, + required ChangeOperation op, + required DateTime modifiedAt, + required String clientMutationId, + Map? payload, + }) = _ChangeEnvelope; + + factory ChangeEnvelope.fromJson(Map json) => + _$ChangeEnvelopeFromJson(json); +} + +/// Push request for batched local changes. +@freezed +abstract class SyncPushRequest with _$SyncPushRequest { + @JsonSerializable(explicitToJson: true) + const factory SyncPushRequest({ + required String deviceId, + String? cursor, + required List changes, + required String idempotencyKey, + }) = _SyncPushRequest; + + factory SyncPushRequest.fromJson(Map json) => + _$SyncPushRequestFromJson(json); +} + +/// Acknowledgement for an accepted mutation. +@freezed +abstract class MutationAck with _$MutationAck { + const factory MutationAck({ + required String clientMutationId, + String? serverMutationId, + DateTime? acceptedAt, + }) = _MutationAck; + + factory MutationAck.fromJson(Map json) => + _$MutationAckFromJson(json); +} + +/// Rejection detail for a mutation. +@freezed +abstract class MutationRejection with _$MutationRejection { + const factory MutationRejection({ + required String clientMutationId, + required String code, + required String message, + }) = _MutationRejection; + + factory MutationRejection.fromJson(Map json) => + _$MutationRejectionFromJson(json); +} + +/// Result body returned by sync push. +@freezed +abstract class SyncPushResult with _$SyncPushResult { + @JsonSerializable(explicitToJson: true) + const factory SyncPushResult({ + required String cursor, + @Default([]) List accepted, + @Default([]) List rejected, + }) = _SyncPushResult; + + factory SyncPushResult.fromJson(Map json) => + _$SyncPushResultFromJson(json); +} + +/// Result body returned by sync pull. +@freezed +abstract class SyncPullResult with _$SyncPullResult { + @JsonSerializable(explicitToJson: true) + const factory SyncPullResult({ + required String cursor, + @Default([]) List changes, + }) = _SyncPullResult; + + factory SyncPullResult.fromJson(Map json) => + _$SyncPullResultFromJson(json); +} + +/// Signals feed item. +@freezed +abstract class SignalItem with _$SignalItem { + const factory SignalItem({ + required String id, + required String type, + required String title, + String? description, + String? personId, + required DateTime createdAt, + Map? metadata, + }) = _SignalItem; + + factory SignalItem.fromJson(Map json) => + _$SignalItemFromJson(json); +} + +/// Cursor-based signals response. +@freezed +abstract class SignalsFeed with _$SignalsFeed { + @JsonSerializable(explicitToJson: true) + const factory SignalsFeed({ + String? cursor, + @Default([]) List items, + }) = _SignalsFeed; + + factory SignalsFeed.fromJson(Map json) => + _$SignalsFeedFromJson(json); +} + +/// Signal acknowledgement request body. +@freezed +abstract class SignalAckRequest with _$SignalAckRequest { + const factory SignalAckRequest({ + required SignalAction action, + required DateTime at, + }) = _SignalAckRequest; + + factory SignalAckRequest.fromJson(Map json) => + _$SignalAckRequestFromJson(json); +} + +/// Upload init request. +@freezed +abstract class UploadInitRequest with _$UploadInitRequest { + const factory UploadInitRequest({ + required UploadPurpose purpose, + required String contentType, + required int sizeBytes, + }) = _UploadInitRequest; + + factory UploadInitRequest.fromJson(Map json) => + _$UploadInitRequestFromJson(json); +} + +/// Upload init response. +@freezed +abstract class UploadInitResult with _$UploadInitResult { + const factory UploadInitResult({ + required String uploadUrl, + required String fileId, + required DateTime expiresAt, + }) = _UploadInitResult; + + factory UploadInitResult.fromJson(Map json) => + _$UploadInitResultFromJson(json); +} + +/// Download URL response. +@freezed +abstract class DownloadUrl with _$DownloadUrl { + const factory DownloadUrl({ + required String downloadUrl, + required DateTime expiresAt, + }) = _DownloadUrl; + + factory DownloadUrl.fromJson(Map json) => + _$DownloadUrlFromJson(json); +} diff --git a/lib/integrations/backend/models/backend_models.freezed.dart b/lib/integrations/backend/models/backend_models.freezed.dart new file mode 100644 index 0000000..b34c8b9 --- /dev/null +++ b/lib/integrations/backend/models/backend_models.freezed.dart @@ -0,0 +1,4669 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'backend_models.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$SignInRequest { + + SignInMethod get method; String? get email; String? get password; String? get idToken; +/// Create a copy of SignInRequest +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SignInRequestCopyWith get copyWith => _$SignInRequestCopyWithImpl(this as SignInRequest, _$identity); + + /// Serializes this SignInRequest to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SignInRequest&&(identical(other.method, method) || other.method == method)&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.idToken, idToken) || other.idToken == idToken)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,method,email,password,idToken); + +@override +String toString() { + return 'SignInRequest(method: $method, email: $email, password: $password, idToken: $idToken)'; +} + + +} + +/// @nodoc +abstract mixin class $SignInRequestCopyWith<$Res> { + factory $SignInRequestCopyWith(SignInRequest value, $Res Function(SignInRequest) _then) = _$SignInRequestCopyWithImpl; +@useResult +$Res call({ + SignInMethod method, String? email, String? password, String? idToken +}); + + + + +} +/// @nodoc +class _$SignInRequestCopyWithImpl<$Res> + implements $SignInRequestCopyWith<$Res> { + _$SignInRequestCopyWithImpl(this._self, this._then); + + final SignInRequest _self; + final $Res Function(SignInRequest) _then; + +/// Create a copy of SignInRequest +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? method = null,Object? email = freezed,Object? password = freezed,Object? idToken = freezed,}) { + return _then(_self.copyWith( +method: null == method ? _self.method : method // ignore: cast_nullable_to_non_nullable +as SignInMethod,email: freezed == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String?,password: freezed == password ? _self.password : password // ignore: cast_nullable_to_non_nullable +as String?,idToken: freezed == idToken ? _self.idToken : idToken // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SignInRequest]. +extension SignInRequestPatterns on SignInRequest { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SignInRequest value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SignInRequest() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SignInRequest value) $default,){ +final _that = this; +switch (_that) { +case _SignInRequest(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SignInRequest value)? $default,){ +final _that = this; +switch (_that) { +case _SignInRequest() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( SignInMethod method, String? email, String? password, String? idToken)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SignInRequest() when $default != null: +return $default(_that.method,_that.email,_that.password,_that.idToken);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( SignInMethod method, String? email, String? password, String? idToken) $default,) {final _that = this; +switch (_that) { +case _SignInRequest(): +return $default(_that.method,_that.email,_that.password,_that.idToken);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( SignInMethod method, String? email, String? password, String? idToken)? $default,) {final _that = this; +switch (_that) { +case _SignInRequest() when $default != null: +return $default(_that.method,_that.email,_that.password,_that.idToken);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SignInRequest implements SignInRequest { + const _SignInRequest({required this.method, this.email, this.password, this.idToken}); + factory _SignInRequest.fromJson(Map json) => _$SignInRequestFromJson(json); + +@override final SignInMethod method; +@override final String? email; +@override final String? password; +@override final String? idToken; + +/// Create a copy of SignInRequest +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SignInRequestCopyWith<_SignInRequest> get copyWith => __$SignInRequestCopyWithImpl<_SignInRequest>(this, _$identity); + +@override +Map toJson() { + return _$SignInRequestToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SignInRequest&&(identical(other.method, method) || other.method == method)&&(identical(other.email, email) || other.email == email)&&(identical(other.password, password) || other.password == password)&&(identical(other.idToken, idToken) || other.idToken == idToken)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,method,email,password,idToken); + +@override +String toString() { + return 'SignInRequest(method: $method, email: $email, password: $password, idToken: $idToken)'; +} + + +} + +/// @nodoc +abstract mixin class _$SignInRequestCopyWith<$Res> implements $SignInRequestCopyWith<$Res> { + factory _$SignInRequestCopyWith(_SignInRequest value, $Res Function(_SignInRequest) _then) = __$SignInRequestCopyWithImpl; +@override @useResult +$Res call({ + SignInMethod method, String? email, String? password, String? idToken +}); + + + + +} +/// @nodoc +class __$SignInRequestCopyWithImpl<$Res> + implements _$SignInRequestCopyWith<$Res> { + __$SignInRequestCopyWithImpl(this._self, this._then); + + final _SignInRequest _self; + final $Res Function(_SignInRequest) _then; + +/// Create a copy of SignInRequest +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? method = null,Object? email = freezed,Object? password = freezed,Object? idToken = freezed,}) { + return _then(_SignInRequest( +method: null == method ? _self.method : method // ignore: cast_nullable_to_non_nullable +as SignInMethod,email: freezed == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String?,password: freezed == password ? _self.password : password // ignore: cast_nullable_to_non_nullable +as String?,idToken: freezed == idToken ? _self.idToken : idToken // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$UserProfile { + + String get id; String? get email; String? get displayName; String? get avatarFileId; +/// Create a copy of UserProfile +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$UserProfileCopyWith get copyWith => _$UserProfileCopyWithImpl(this as UserProfile, _$identity); + + /// Serializes this UserProfile to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is UserProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.email, email) || other.email == email)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.avatarFileId, avatarFileId) || other.avatarFileId == avatarFileId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,email,displayName,avatarFileId); + +@override +String toString() { + return 'UserProfile(id: $id, email: $email, displayName: $displayName, avatarFileId: $avatarFileId)'; +} + + +} + +/// @nodoc +abstract mixin class $UserProfileCopyWith<$Res> { + factory $UserProfileCopyWith(UserProfile value, $Res Function(UserProfile) _then) = _$UserProfileCopyWithImpl; +@useResult +$Res call({ + String id, String? email, String? displayName, String? avatarFileId +}); + + + + +} +/// @nodoc +class _$UserProfileCopyWithImpl<$Res> + implements $UserProfileCopyWith<$Res> { + _$UserProfileCopyWithImpl(this._self, this._then); + + final UserProfile _self; + final $Res Function(UserProfile) _then; + +/// Create a copy of UserProfile +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? email = freezed,Object? displayName = freezed,Object? avatarFileId = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,email: freezed == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String?,displayName: freezed == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable +as String?,avatarFileId: freezed == avatarFileId ? _self.avatarFileId : avatarFileId // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [UserProfile]. +extension UserProfilePatterns on UserProfile { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _UserProfile value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _UserProfile() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _UserProfile value) $default,){ +final _that = this; +switch (_that) { +case _UserProfile(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _UserProfile value)? $default,){ +final _that = this; +switch (_that) { +case _UserProfile() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String? email, String? displayName, String? avatarFileId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _UserProfile() when $default != null: +return $default(_that.id,_that.email,_that.displayName,_that.avatarFileId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String? email, String? displayName, String? avatarFileId) $default,) {final _that = this; +switch (_that) { +case _UserProfile(): +return $default(_that.id,_that.email,_that.displayName,_that.avatarFileId);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String? email, String? displayName, String? avatarFileId)? $default,) {final _that = this; +switch (_that) { +case _UserProfile() when $default != null: +return $default(_that.id,_that.email,_that.displayName,_that.avatarFileId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _UserProfile implements UserProfile { + const _UserProfile({required this.id, this.email, this.displayName, this.avatarFileId}); + factory _UserProfile.fromJson(Map json) => _$UserProfileFromJson(json); + +@override final String id; +@override final String? email; +@override final String? displayName; +@override final String? avatarFileId; + +/// Create a copy of UserProfile +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$UserProfileCopyWith<_UserProfile> get copyWith => __$UserProfileCopyWithImpl<_UserProfile>(this, _$identity); + +@override +Map toJson() { + return _$UserProfileToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _UserProfile&&(identical(other.id, id) || other.id == id)&&(identical(other.email, email) || other.email == email)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.avatarFileId, avatarFileId) || other.avatarFileId == avatarFileId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,email,displayName,avatarFileId); + +@override +String toString() { + return 'UserProfile(id: $id, email: $email, displayName: $displayName, avatarFileId: $avatarFileId)'; +} + + +} + +/// @nodoc +abstract mixin class _$UserProfileCopyWith<$Res> implements $UserProfileCopyWith<$Res> { + factory _$UserProfileCopyWith(_UserProfile value, $Res Function(_UserProfile) _then) = __$UserProfileCopyWithImpl; +@override @useResult +$Res call({ + String id, String? email, String? displayName, String? avatarFileId +}); + + + + +} +/// @nodoc +class __$UserProfileCopyWithImpl<$Res> + implements _$UserProfileCopyWith<$Res> { + __$UserProfileCopyWithImpl(this._self, this._then); + + final _UserProfile _self; + final $Res Function(_UserProfile) _then; + +/// Create a copy of UserProfile +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? email = freezed,Object? displayName = freezed,Object? avatarFileId = freezed,}) { + return _then(_UserProfile( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,email: freezed == email ? _self.email : email // ignore: cast_nullable_to_non_nullable +as String?,displayName: freezed == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable +as String?,avatarFileId: freezed == avatarFileId ? _self.avatarFileId : avatarFileId // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + + +} + + +/// @nodoc +mixin _$AuthSession { + + String get accessToken; String get refreshToken; DateTime get expiresAt; UserProfile get user; +/// Create a copy of AuthSession +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthSessionCopyWith get copyWith => _$AuthSessionCopyWithImpl(this as AuthSession, _$identity); + + /// Serializes this AuthSession to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthSession&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)&&(identical(other.user, user) || other.user == user)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,expiresAt,user); + +@override +String toString() { + return 'AuthSession(accessToken: $accessToken, refreshToken: $refreshToken, expiresAt: $expiresAt, user: $user)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthSessionCopyWith<$Res> { + factory $AuthSessionCopyWith(AuthSession value, $Res Function(AuthSession) _then) = _$AuthSessionCopyWithImpl; +@useResult +$Res call({ + String accessToken, String refreshToken, DateTime expiresAt, UserProfile user +}); + + +$UserProfileCopyWith<$Res> get user; + +} +/// @nodoc +class _$AuthSessionCopyWithImpl<$Res> + implements $AuthSessionCopyWith<$Res> { + _$AuthSessionCopyWithImpl(this._self, this._then); + + final AuthSession _self; + final $Res Function(AuthSession) _then; + +/// Create a copy of AuthSession +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = null,Object? refreshToken = null,Object? expiresAt = null,Object? user = null,}) { + return _then(_self.copyWith( +accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable +as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime,user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable +as UserProfile, + )); +} +/// Create a copy of AuthSession +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$UserProfileCopyWith<$Res> get user { + + return $UserProfileCopyWith<$Res>(_self.user, (value) { + return _then(_self.copyWith(user: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [AuthSession]. +extension AuthSessionPatterns on AuthSession { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _AuthSession value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AuthSession() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _AuthSession value) $default,){ +final _that = this; +switch (_that) { +case _AuthSession(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AuthSession value)? $default,){ +final _that = this; +switch (_that) { +case _AuthSession() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String accessToken, String refreshToken, DateTime expiresAt, UserProfile user)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AuthSession() when $default != null: +return $default(_that.accessToken,_that.refreshToken,_that.expiresAt,_that.user);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String accessToken, String refreshToken, DateTime expiresAt, UserProfile user) $default,) {final _that = this; +switch (_that) { +case _AuthSession(): +return $default(_that.accessToken,_that.refreshToken,_that.expiresAt,_that.user);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String accessToken, String refreshToken, DateTime expiresAt, UserProfile user)? $default,) {final _that = this; +switch (_that) { +case _AuthSession() when $default != null: +return $default(_that.accessToken,_that.refreshToken,_that.expiresAt,_that.user);case _: + return null; + +} +} + +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _AuthSession implements AuthSession { + const _AuthSession({required this.accessToken, required this.refreshToken, required this.expiresAt, required this.user}); + factory _AuthSession.fromJson(Map json) => _$AuthSessionFromJson(json); + +@override final String accessToken; +@override final String refreshToken; +@override final DateTime expiresAt; +@override final UserProfile user; + +/// Create a copy of AuthSession +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AuthSessionCopyWith<_AuthSession> get copyWith => __$AuthSessionCopyWithImpl<_AuthSession>(this, _$identity); + +@override +Map toJson() { + return _$AuthSessionToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthSession&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)&&(identical(other.user, user) || other.user == user)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,expiresAt,user); + +@override +String toString() { + return 'AuthSession(accessToken: $accessToken, refreshToken: $refreshToken, expiresAt: $expiresAt, user: $user)'; +} + + +} + +/// @nodoc +abstract mixin class _$AuthSessionCopyWith<$Res> implements $AuthSessionCopyWith<$Res> { + factory _$AuthSessionCopyWith(_AuthSession value, $Res Function(_AuthSession) _then) = __$AuthSessionCopyWithImpl; +@override @useResult +$Res call({ + String accessToken, String refreshToken, DateTime expiresAt, UserProfile user +}); + + +@override $UserProfileCopyWith<$Res> get user; + +} +/// @nodoc +class __$AuthSessionCopyWithImpl<$Res> + implements _$AuthSessionCopyWith<$Res> { + __$AuthSessionCopyWithImpl(this._self, this._then); + + final _AuthSession _self; + final $Res Function(_AuthSession) _then; + +/// Create a copy of AuthSession +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = null,Object? refreshToken = null,Object? expiresAt = null,Object? user = null,}) { + return _then(_AuthSession( +accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable +as String,refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime,user: null == user ? _self.user : user // ignore: cast_nullable_to_non_nullable +as UserProfile, + )); +} + +/// Create a copy of AuthSession +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$UserProfileCopyWith<$Res> get user { + + return $UserProfileCopyWith<$Res>(_self.user, (value) { + return _then(_self.copyWith(user: value)); + }); +} +} + + +/// @nodoc +mixin _$AuthRefreshRequest { + + String get refreshToken; +/// Create a copy of AuthRefreshRequest +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthRefreshRequestCopyWith get copyWith => _$AuthRefreshRequestCopyWithImpl(this as AuthRefreshRequest, _$identity); + + /// Serializes this AuthRefreshRequest to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthRefreshRequest&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,refreshToken); + +@override +String toString() { + return 'AuthRefreshRequest(refreshToken: $refreshToken)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthRefreshRequestCopyWith<$Res> { + factory $AuthRefreshRequestCopyWith(AuthRefreshRequest value, $Res Function(AuthRefreshRequest) _then) = _$AuthRefreshRequestCopyWithImpl; +@useResult +$Res call({ + String refreshToken +}); + + + + +} +/// @nodoc +class _$AuthRefreshRequestCopyWithImpl<$Res> + implements $AuthRefreshRequestCopyWith<$Res> { + _$AuthRefreshRequestCopyWithImpl(this._self, this._then); + + final AuthRefreshRequest _self; + final $Res Function(AuthRefreshRequest) _then; + +/// Create a copy of AuthRefreshRequest +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? refreshToken = null,}) { + return _then(_self.copyWith( +refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [AuthRefreshRequest]. +extension AuthRefreshRequestPatterns on AuthRefreshRequest { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _AuthRefreshRequest value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AuthRefreshRequest() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _AuthRefreshRequest value) $default,){ +final _that = this; +switch (_that) { +case _AuthRefreshRequest(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AuthRefreshRequest value)? $default,){ +final _that = this; +switch (_that) { +case _AuthRefreshRequest() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String refreshToken)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AuthRefreshRequest() when $default != null: +return $default(_that.refreshToken);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String refreshToken) $default,) {final _that = this; +switch (_that) { +case _AuthRefreshRequest(): +return $default(_that.refreshToken);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String refreshToken)? $default,) {final _that = this; +switch (_that) { +case _AuthRefreshRequest() when $default != null: +return $default(_that.refreshToken);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _AuthRefreshRequest implements AuthRefreshRequest { + const _AuthRefreshRequest({required this.refreshToken}); + factory _AuthRefreshRequest.fromJson(Map json) => _$AuthRefreshRequestFromJson(json); + +@override final String refreshToken; + +/// Create a copy of AuthRefreshRequest +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AuthRefreshRequestCopyWith<_AuthRefreshRequest> get copyWith => __$AuthRefreshRequestCopyWithImpl<_AuthRefreshRequest>(this, _$identity); + +@override +Map toJson() { + return _$AuthRefreshRequestToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthRefreshRequest&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,refreshToken); + +@override +String toString() { + return 'AuthRefreshRequest(refreshToken: $refreshToken)'; +} + + +} + +/// @nodoc +abstract mixin class _$AuthRefreshRequestCopyWith<$Res> implements $AuthRefreshRequestCopyWith<$Res> { + factory _$AuthRefreshRequestCopyWith(_AuthRefreshRequest value, $Res Function(_AuthRefreshRequest) _then) = __$AuthRefreshRequestCopyWithImpl; +@override @useResult +$Res call({ + String refreshToken +}); + + + + +} +/// @nodoc +class __$AuthRefreshRequestCopyWithImpl<$Res> + implements _$AuthRefreshRequestCopyWith<$Res> { + __$AuthRefreshRequestCopyWithImpl(this._self, this._then); + + final _AuthRefreshRequest _self; + final $Res Function(_AuthRefreshRequest) _then; + +/// Create a copy of AuthRefreshRequest +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? refreshToken = null,}) { + return _then(_AuthRefreshRequest( +refreshToken: null == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$AuthRefreshResponse { + + String get accessToken; String? get refreshToken; DateTime get expiresAt; +/// Create a copy of AuthRefreshResponse +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AuthRefreshResponseCopyWith get copyWith => _$AuthRefreshResponseCopyWithImpl(this as AuthRefreshResponse, _$identity); + + /// Serializes this AuthRefreshResponse to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AuthRefreshResponse&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,expiresAt); + +@override +String toString() { + return 'AuthRefreshResponse(accessToken: $accessToken, refreshToken: $refreshToken, expiresAt: $expiresAt)'; +} + + +} + +/// @nodoc +abstract mixin class $AuthRefreshResponseCopyWith<$Res> { + factory $AuthRefreshResponseCopyWith(AuthRefreshResponse value, $Res Function(AuthRefreshResponse) _then) = _$AuthRefreshResponseCopyWithImpl; +@useResult +$Res call({ + String accessToken, String? refreshToken, DateTime expiresAt +}); + + + + +} +/// @nodoc +class _$AuthRefreshResponseCopyWithImpl<$Res> + implements $AuthRefreshResponseCopyWith<$Res> { + _$AuthRefreshResponseCopyWithImpl(this._self, this._then); + + final AuthRefreshResponse _self; + final $Res Function(AuthRefreshResponse) _then; + +/// Create a copy of AuthRefreshResponse +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? accessToken = null,Object? refreshToken = freezed,Object? expiresAt = null,}) { + return _then(_self.copyWith( +accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable +as String,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String?,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + +} + + +/// Adds pattern-matching-related methods to [AuthRefreshResponse]. +extension AuthRefreshResponsePatterns on AuthRefreshResponse { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _AuthRefreshResponse value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AuthRefreshResponse() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _AuthRefreshResponse value) $default,){ +final _that = this; +switch (_that) { +case _AuthRefreshResponse(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AuthRefreshResponse value)? $default,){ +final _that = this; +switch (_that) { +case _AuthRefreshResponse() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String accessToken, String? refreshToken, DateTime expiresAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AuthRefreshResponse() when $default != null: +return $default(_that.accessToken,_that.refreshToken,_that.expiresAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String accessToken, String? refreshToken, DateTime expiresAt) $default,) {final _that = this; +switch (_that) { +case _AuthRefreshResponse(): +return $default(_that.accessToken,_that.refreshToken,_that.expiresAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String accessToken, String? refreshToken, DateTime expiresAt)? $default,) {final _that = this; +switch (_that) { +case _AuthRefreshResponse() when $default != null: +return $default(_that.accessToken,_that.refreshToken,_that.expiresAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _AuthRefreshResponse implements AuthRefreshResponse { + const _AuthRefreshResponse({required this.accessToken, this.refreshToken, required this.expiresAt}); + factory _AuthRefreshResponse.fromJson(Map json) => _$AuthRefreshResponseFromJson(json); + +@override final String accessToken; +@override final String? refreshToken; +@override final DateTime expiresAt; + +/// Create a copy of AuthRefreshResponse +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AuthRefreshResponseCopyWith<_AuthRefreshResponse> get copyWith => __$AuthRefreshResponseCopyWithImpl<_AuthRefreshResponse>(this, _$identity); + +@override +Map toJson() { + return _$AuthRefreshResponseToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AuthRefreshResponse&&(identical(other.accessToken, accessToken) || other.accessToken == accessToken)&&(identical(other.refreshToken, refreshToken) || other.refreshToken == refreshToken)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,accessToken,refreshToken,expiresAt); + +@override +String toString() { + return 'AuthRefreshResponse(accessToken: $accessToken, refreshToken: $refreshToken, expiresAt: $expiresAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$AuthRefreshResponseCopyWith<$Res> implements $AuthRefreshResponseCopyWith<$Res> { + factory _$AuthRefreshResponseCopyWith(_AuthRefreshResponse value, $Res Function(_AuthRefreshResponse) _then) = __$AuthRefreshResponseCopyWithImpl; +@override @useResult +$Res call({ + String accessToken, String? refreshToken, DateTime expiresAt +}); + + + + +} +/// @nodoc +class __$AuthRefreshResponseCopyWithImpl<$Res> + implements _$AuthRefreshResponseCopyWith<$Res> { + __$AuthRefreshResponseCopyWithImpl(this._self, this._then); + + final _AuthRefreshResponse _self; + final $Res Function(_AuthRefreshResponse) _then; + +/// Create a copy of AuthRefreshResponse +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? accessToken = null,Object? refreshToken = freezed,Object? expiresAt = null,}) { + return _then(_AuthRefreshResponse( +accessToken: null == accessToken ? _self.accessToken : accessToken // ignore: cast_nullable_to_non_nullable +as String,refreshToken: freezed == refreshToken ? _self.refreshToken : refreshToken // ignore: cast_nullable_to_non_nullable +as String?,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + + +} + + +/// @nodoc +mixin _$ChangeEnvelope { + + int get schemaVersion; String get entityType; String get entityId; ChangeOperation get op; DateTime get modifiedAt; String get clientMutationId; Map? get payload; +/// Create a copy of ChangeEnvelope +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ChangeEnvelopeCopyWith get copyWith => _$ChangeEnvelopeCopyWithImpl(this as ChangeEnvelope, _$identity); + + /// Serializes this ChangeEnvelope to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ChangeEnvelope&&(identical(other.schemaVersion, schemaVersion) || other.schemaVersion == schemaVersion)&&(identical(other.entityType, entityType) || other.entityType == entityType)&&(identical(other.entityId, entityId) || other.entityId == entityId)&&(identical(other.op, op) || other.op == op)&&(identical(other.modifiedAt, modifiedAt) || other.modifiedAt == modifiedAt)&&(identical(other.clientMutationId, clientMutationId) || other.clientMutationId == clientMutationId)&&const DeepCollectionEquality().equals(other.payload, payload)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,schemaVersion,entityType,entityId,op,modifiedAt,clientMutationId,const DeepCollectionEquality().hash(payload)); + +@override +String toString() { + return 'ChangeEnvelope(schemaVersion: $schemaVersion, entityType: $entityType, entityId: $entityId, op: $op, modifiedAt: $modifiedAt, clientMutationId: $clientMutationId, payload: $payload)'; +} + + +} + +/// @nodoc +abstract mixin class $ChangeEnvelopeCopyWith<$Res> { + factory $ChangeEnvelopeCopyWith(ChangeEnvelope value, $Res Function(ChangeEnvelope) _then) = _$ChangeEnvelopeCopyWithImpl; +@useResult +$Res call({ + int schemaVersion, String entityType, String entityId, ChangeOperation op, DateTime modifiedAt, String clientMutationId, Map? payload +}); + + + + +} +/// @nodoc +class _$ChangeEnvelopeCopyWithImpl<$Res> + implements $ChangeEnvelopeCopyWith<$Res> { + _$ChangeEnvelopeCopyWithImpl(this._self, this._then); + + final ChangeEnvelope _self; + final $Res Function(ChangeEnvelope) _then; + +/// Create a copy of ChangeEnvelope +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? schemaVersion = null,Object? entityType = null,Object? entityId = null,Object? op = null,Object? modifiedAt = null,Object? clientMutationId = null,Object? payload = freezed,}) { + return _then(_self.copyWith( +schemaVersion: null == schemaVersion ? _self.schemaVersion : schemaVersion // ignore: cast_nullable_to_non_nullable +as int,entityType: null == entityType ? _self.entityType : entityType // ignore: cast_nullable_to_non_nullable +as String,entityId: null == entityId ? _self.entityId : entityId // ignore: cast_nullable_to_non_nullable +as String,op: null == op ? _self.op : op // ignore: cast_nullable_to_non_nullable +as ChangeOperation,modifiedAt: null == modifiedAt ? _self.modifiedAt : modifiedAt // ignore: cast_nullable_to_non_nullable +as DateTime,clientMutationId: null == clientMutationId ? _self.clientMutationId : clientMutationId // ignore: cast_nullable_to_non_nullable +as String,payload: freezed == payload ? _self.payload : payload // ignore: cast_nullable_to_non_nullable +as Map?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ChangeEnvelope]. +extension ChangeEnvelopePatterns on ChangeEnvelope { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ChangeEnvelope value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ChangeEnvelope() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ChangeEnvelope value) $default,){ +final _that = this; +switch (_that) { +case _ChangeEnvelope(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ChangeEnvelope value)? $default,){ +final _that = this; +switch (_that) { +case _ChangeEnvelope() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( int schemaVersion, String entityType, String entityId, ChangeOperation op, DateTime modifiedAt, String clientMutationId, Map? payload)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ChangeEnvelope() when $default != null: +return $default(_that.schemaVersion,_that.entityType,_that.entityId,_that.op,_that.modifiedAt,_that.clientMutationId,_that.payload);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( int schemaVersion, String entityType, String entityId, ChangeOperation op, DateTime modifiedAt, String clientMutationId, Map? payload) $default,) {final _that = this; +switch (_that) { +case _ChangeEnvelope(): +return $default(_that.schemaVersion,_that.entityType,_that.entityId,_that.op,_that.modifiedAt,_that.clientMutationId,_that.payload);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( int schemaVersion, String entityType, String entityId, ChangeOperation op, DateTime modifiedAt, String clientMutationId, Map? payload)? $default,) {final _that = this; +switch (_that) { +case _ChangeEnvelope() when $default != null: +return $default(_that.schemaVersion,_that.entityType,_that.entityId,_that.op,_that.modifiedAt,_that.clientMutationId,_that.payload);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ChangeEnvelope implements ChangeEnvelope { + const _ChangeEnvelope({required this.schemaVersion, required this.entityType, required this.entityId, required this.op, required this.modifiedAt, required this.clientMutationId, final Map? payload}): _payload = payload; + factory _ChangeEnvelope.fromJson(Map json) => _$ChangeEnvelopeFromJson(json); + +@override final int schemaVersion; +@override final String entityType; +@override final String entityId; +@override final ChangeOperation op; +@override final DateTime modifiedAt; +@override final String clientMutationId; + final Map? _payload; +@override Map? get payload { + final value = _payload; + if (value == null) return null; + if (_payload is EqualUnmodifiableMapView) return _payload; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); +} + + +/// Create a copy of ChangeEnvelope +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ChangeEnvelopeCopyWith<_ChangeEnvelope> get copyWith => __$ChangeEnvelopeCopyWithImpl<_ChangeEnvelope>(this, _$identity); + +@override +Map toJson() { + return _$ChangeEnvelopeToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ChangeEnvelope&&(identical(other.schemaVersion, schemaVersion) || other.schemaVersion == schemaVersion)&&(identical(other.entityType, entityType) || other.entityType == entityType)&&(identical(other.entityId, entityId) || other.entityId == entityId)&&(identical(other.op, op) || other.op == op)&&(identical(other.modifiedAt, modifiedAt) || other.modifiedAt == modifiedAt)&&(identical(other.clientMutationId, clientMutationId) || other.clientMutationId == clientMutationId)&&const DeepCollectionEquality().equals(other._payload, _payload)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,schemaVersion,entityType,entityId,op,modifiedAt,clientMutationId,const DeepCollectionEquality().hash(_payload)); + +@override +String toString() { + return 'ChangeEnvelope(schemaVersion: $schemaVersion, entityType: $entityType, entityId: $entityId, op: $op, modifiedAt: $modifiedAt, clientMutationId: $clientMutationId, payload: $payload)'; +} + + +} + +/// @nodoc +abstract mixin class _$ChangeEnvelopeCopyWith<$Res> implements $ChangeEnvelopeCopyWith<$Res> { + factory _$ChangeEnvelopeCopyWith(_ChangeEnvelope value, $Res Function(_ChangeEnvelope) _then) = __$ChangeEnvelopeCopyWithImpl; +@override @useResult +$Res call({ + int schemaVersion, String entityType, String entityId, ChangeOperation op, DateTime modifiedAt, String clientMutationId, Map? payload +}); + + + + +} +/// @nodoc +class __$ChangeEnvelopeCopyWithImpl<$Res> + implements _$ChangeEnvelopeCopyWith<$Res> { + __$ChangeEnvelopeCopyWithImpl(this._self, this._then); + + final _ChangeEnvelope _self; + final $Res Function(_ChangeEnvelope) _then; + +/// Create a copy of ChangeEnvelope +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? schemaVersion = null,Object? entityType = null,Object? entityId = null,Object? op = null,Object? modifiedAt = null,Object? clientMutationId = null,Object? payload = freezed,}) { + return _then(_ChangeEnvelope( +schemaVersion: null == schemaVersion ? _self.schemaVersion : schemaVersion // ignore: cast_nullable_to_non_nullable +as int,entityType: null == entityType ? _self.entityType : entityType // ignore: cast_nullable_to_non_nullable +as String,entityId: null == entityId ? _self.entityId : entityId // ignore: cast_nullable_to_non_nullable +as String,op: null == op ? _self.op : op // ignore: cast_nullable_to_non_nullable +as ChangeOperation,modifiedAt: null == modifiedAt ? _self.modifiedAt : modifiedAt // ignore: cast_nullable_to_non_nullable +as DateTime,clientMutationId: null == clientMutationId ? _self.clientMutationId : clientMutationId // ignore: cast_nullable_to_non_nullable +as String,payload: freezed == payload ? _self._payload : payload // ignore: cast_nullable_to_non_nullable +as Map?, + )); +} + + +} + + +/// @nodoc +mixin _$SyncPushRequest { + + String get deviceId; String? get cursor; List get changes; String get idempotencyKey; +/// Create a copy of SyncPushRequest +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SyncPushRequestCopyWith get copyWith => _$SyncPushRequestCopyWithImpl(this as SyncPushRequest, _$identity); + + /// Serializes this SyncPushRequest to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SyncPushRequest&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other.changes, changes)&&(identical(other.idempotencyKey, idempotencyKey) || other.idempotencyKey == idempotencyKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,deviceId,cursor,const DeepCollectionEquality().hash(changes),idempotencyKey); + +@override +String toString() { + return 'SyncPushRequest(deviceId: $deviceId, cursor: $cursor, changes: $changes, idempotencyKey: $idempotencyKey)'; +} + + +} + +/// @nodoc +abstract mixin class $SyncPushRequestCopyWith<$Res> { + factory $SyncPushRequestCopyWith(SyncPushRequest value, $Res Function(SyncPushRequest) _then) = _$SyncPushRequestCopyWithImpl; +@useResult +$Res call({ + String deviceId, String? cursor, List changes, String idempotencyKey +}); + + + + +} +/// @nodoc +class _$SyncPushRequestCopyWithImpl<$Res> + implements $SyncPushRequestCopyWith<$Res> { + _$SyncPushRequestCopyWithImpl(this._self, this._then); + + final SyncPushRequest _self; + final $Res Function(SyncPushRequest) _then; + +/// Create a copy of SyncPushRequest +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? cursor = freezed,Object? changes = null,Object? idempotencyKey = null,}) { + return _then(_self.copyWith( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,cursor: freezed == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String?,changes: null == changes ? _self.changes : changes // ignore: cast_nullable_to_non_nullable +as List,idempotencyKey: null == idempotencyKey ? _self.idempotencyKey : idempotencyKey // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SyncPushRequest]. +extension SyncPushRequestPatterns on SyncPushRequest { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SyncPushRequest value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SyncPushRequest() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SyncPushRequest value) $default,){ +final _that = this; +switch (_that) { +case _SyncPushRequest(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SyncPushRequest value)? $default,){ +final _that = this; +switch (_that) { +case _SyncPushRequest() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String deviceId, String? cursor, List changes, String idempotencyKey)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SyncPushRequest() when $default != null: +return $default(_that.deviceId,_that.cursor,_that.changes,_that.idempotencyKey);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String deviceId, String? cursor, List changes, String idempotencyKey) $default,) {final _that = this; +switch (_that) { +case _SyncPushRequest(): +return $default(_that.deviceId,_that.cursor,_that.changes,_that.idempotencyKey);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String deviceId, String? cursor, List changes, String idempotencyKey)? $default,) {final _that = this; +switch (_that) { +case _SyncPushRequest() when $default != null: +return $default(_that.deviceId,_that.cursor,_that.changes,_that.idempotencyKey);case _: + return null; + +} +} + +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _SyncPushRequest implements SyncPushRequest { + const _SyncPushRequest({required this.deviceId, this.cursor, required final List changes, required this.idempotencyKey}): _changes = changes; + factory _SyncPushRequest.fromJson(Map json) => _$SyncPushRequestFromJson(json); + +@override final String deviceId; +@override final String? cursor; + final List _changes; +@override List get changes { + if (_changes is EqualUnmodifiableListView) return _changes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_changes); +} + +@override final String idempotencyKey; + +/// Create a copy of SyncPushRequest +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SyncPushRequestCopyWith<_SyncPushRequest> get copyWith => __$SyncPushRequestCopyWithImpl<_SyncPushRequest>(this, _$identity); + +@override +Map toJson() { + return _$SyncPushRequestToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SyncPushRequest&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other._changes, _changes)&&(identical(other.idempotencyKey, idempotencyKey) || other.idempotencyKey == idempotencyKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,deviceId,cursor,const DeepCollectionEquality().hash(_changes),idempotencyKey); + +@override +String toString() { + return 'SyncPushRequest(deviceId: $deviceId, cursor: $cursor, changes: $changes, idempotencyKey: $idempotencyKey)'; +} + + +} + +/// @nodoc +abstract mixin class _$SyncPushRequestCopyWith<$Res> implements $SyncPushRequestCopyWith<$Res> { + factory _$SyncPushRequestCopyWith(_SyncPushRequest value, $Res Function(_SyncPushRequest) _then) = __$SyncPushRequestCopyWithImpl; +@override @useResult +$Res call({ + String deviceId, String? cursor, List changes, String idempotencyKey +}); + + + + +} +/// @nodoc +class __$SyncPushRequestCopyWithImpl<$Res> + implements _$SyncPushRequestCopyWith<$Res> { + __$SyncPushRequestCopyWithImpl(this._self, this._then); + + final _SyncPushRequest _self; + final $Res Function(_SyncPushRequest) _then; + +/// Create a copy of SyncPushRequest +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? cursor = freezed,Object? changes = null,Object? idempotencyKey = null,}) { + return _then(_SyncPushRequest( +deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable +as String,cursor: freezed == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String?,changes: null == changes ? _self._changes : changes // ignore: cast_nullable_to_non_nullable +as List,idempotencyKey: null == idempotencyKey ? _self.idempotencyKey : idempotencyKey // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$MutationAck { + + String get clientMutationId; String? get serverMutationId; DateTime? get acceptedAt; +/// Create a copy of MutationAck +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$MutationAckCopyWith get copyWith => _$MutationAckCopyWithImpl(this as MutationAck, _$identity); + + /// Serializes this MutationAck to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is MutationAck&&(identical(other.clientMutationId, clientMutationId) || other.clientMutationId == clientMutationId)&&(identical(other.serverMutationId, serverMutationId) || other.serverMutationId == serverMutationId)&&(identical(other.acceptedAt, acceptedAt) || other.acceptedAt == acceptedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,clientMutationId,serverMutationId,acceptedAt); + +@override +String toString() { + return 'MutationAck(clientMutationId: $clientMutationId, serverMutationId: $serverMutationId, acceptedAt: $acceptedAt)'; +} + + +} + +/// @nodoc +abstract mixin class $MutationAckCopyWith<$Res> { + factory $MutationAckCopyWith(MutationAck value, $Res Function(MutationAck) _then) = _$MutationAckCopyWithImpl; +@useResult +$Res call({ + String clientMutationId, String? serverMutationId, DateTime? acceptedAt +}); + + + + +} +/// @nodoc +class _$MutationAckCopyWithImpl<$Res> + implements $MutationAckCopyWith<$Res> { + _$MutationAckCopyWithImpl(this._self, this._then); + + final MutationAck _self; + final $Res Function(MutationAck) _then; + +/// Create a copy of MutationAck +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? clientMutationId = null,Object? serverMutationId = freezed,Object? acceptedAt = freezed,}) { + return _then(_self.copyWith( +clientMutationId: null == clientMutationId ? _self.clientMutationId : clientMutationId // ignore: cast_nullable_to_non_nullable +as String,serverMutationId: freezed == serverMutationId ? _self.serverMutationId : serverMutationId // ignore: cast_nullable_to_non_nullable +as String?,acceptedAt: freezed == acceptedAt ? _self.acceptedAt : acceptedAt // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [MutationAck]. +extension MutationAckPatterns on MutationAck { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _MutationAck value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _MutationAck() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _MutationAck value) $default,){ +final _that = this; +switch (_that) { +case _MutationAck(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _MutationAck value)? $default,){ +final _that = this; +switch (_that) { +case _MutationAck() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String clientMutationId, String? serverMutationId, DateTime? acceptedAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _MutationAck() when $default != null: +return $default(_that.clientMutationId,_that.serverMutationId,_that.acceptedAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String clientMutationId, String? serverMutationId, DateTime? acceptedAt) $default,) {final _that = this; +switch (_that) { +case _MutationAck(): +return $default(_that.clientMutationId,_that.serverMutationId,_that.acceptedAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String clientMutationId, String? serverMutationId, DateTime? acceptedAt)? $default,) {final _that = this; +switch (_that) { +case _MutationAck() when $default != null: +return $default(_that.clientMutationId,_that.serverMutationId,_that.acceptedAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _MutationAck implements MutationAck { + const _MutationAck({required this.clientMutationId, this.serverMutationId, this.acceptedAt}); + factory _MutationAck.fromJson(Map json) => _$MutationAckFromJson(json); + +@override final String clientMutationId; +@override final String? serverMutationId; +@override final DateTime? acceptedAt; + +/// Create a copy of MutationAck +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$MutationAckCopyWith<_MutationAck> get copyWith => __$MutationAckCopyWithImpl<_MutationAck>(this, _$identity); + +@override +Map toJson() { + return _$MutationAckToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _MutationAck&&(identical(other.clientMutationId, clientMutationId) || other.clientMutationId == clientMutationId)&&(identical(other.serverMutationId, serverMutationId) || other.serverMutationId == serverMutationId)&&(identical(other.acceptedAt, acceptedAt) || other.acceptedAt == acceptedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,clientMutationId,serverMutationId,acceptedAt); + +@override +String toString() { + return 'MutationAck(clientMutationId: $clientMutationId, serverMutationId: $serverMutationId, acceptedAt: $acceptedAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$MutationAckCopyWith<$Res> implements $MutationAckCopyWith<$Res> { + factory _$MutationAckCopyWith(_MutationAck value, $Res Function(_MutationAck) _then) = __$MutationAckCopyWithImpl; +@override @useResult +$Res call({ + String clientMutationId, String? serverMutationId, DateTime? acceptedAt +}); + + + + +} +/// @nodoc +class __$MutationAckCopyWithImpl<$Res> + implements _$MutationAckCopyWith<$Res> { + __$MutationAckCopyWithImpl(this._self, this._then); + + final _MutationAck _self; + final $Res Function(_MutationAck) _then; + +/// Create a copy of MutationAck +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? clientMutationId = null,Object? serverMutationId = freezed,Object? acceptedAt = freezed,}) { + return _then(_MutationAck( +clientMutationId: null == clientMutationId ? _self.clientMutationId : clientMutationId // ignore: cast_nullable_to_non_nullable +as String,serverMutationId: freezed == serverMutationId ? _self.serverMutationId : serverMutationId // ignore: cast_nullable_to_non_nullable +as String?,acceptedAt: freezed == acceptedAt ? _self.acceptedAt : acceptedAt // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + + +} + + +/// @nodoc +mixin _$MutationRejection { + + String get clientMutationId; String get code; String get message; +/// Create a copy of MutationRejection +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$MutationRejectionCopyWith get copyWith => _$MutationRejectionCopyWithImpl(this as MutationRejection, _$identity); + + /// Serializes this MutationRejection to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is MutationRejection&&(identical(other.clientMutationId, clientMutationId) || other.clientMutationId == clientMutationId)&&(identical(other.code, code) || other.code == code)&&(identical(other.message, message) || other.message == message)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,clientMutationId,code,message); + +@override +String toString() { + return 'MutationRejection(clientMutationId: $clientMutationId, code: $code, message: $message)'; +} + + +} + +/// @nodoc +abstract mixin class $MutationRejectionCopyWith<$Res> { + factory $MutationRejectionCopyWith(MutationRejection value, $Res Function(MutationRejection) _then) = _$MutationRejectionCopyWithImpl; +@useResult +$Res call({ + String clientMutationId, String code, String message +}); + + + + +} +/// @nodoc +class _$MutationRejectionCopyWithImpl<$Res> + implements $MutationRejectionCopyWith<$Res> { + _$MutationRejectionCopyWithImpl(this._self, this._then); + + final MutationRejection _self; + final $Res Function(MutationRejection) _then; + +/// Create a copy of MutationRejection +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? clientMutationId = null,Object? code = null,Object? message = null,}) { + return _then(_self.copyWith( +clientMutationId: null == clientMutationId ? _self.clientMutationId : clientMutationId // ignore: cast_nullable_to_non_nullable +as String,code: null == code ? _self.code : code // ignore: cast_nullable_to_non_nullable +as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [MutationRejection]. +extension MutationRejectionPatterns on MutationRejection { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _MutationRejection value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _MutationRejection() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _MutationRejection value) $default,){ +final _that = this; +switch (_that) { +case _MutationRejection(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _MutationRejection value)? $default,){ +final _that = this; +switch (_that) { +case _MutationRejection() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String clientMutationId, String code, String message)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _MutationRejection() when $default != null: +return $default(_that.clientMutationId,_that.code,_that.message);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String clientMutationId, String code, String message) $default,) {final _that = this; +switch (_that) { +case _MutationRejection(): +return $default(_that.clientMutationId,_that.code,_that.message);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String clientMutationId, String code, String message)? $default,) {final _that = this; +switch (_that) { +case _MutationRejection() when $default != null: +return $default(_that.clientMutationId,_that.code,_that.message);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _MutationRejection implements MutationRejection { + const _MutationRejection({required this.clientMutationId, required this.code, required this.message}); + factory _MutationRejection.fromJson(Map json) => _$MutationRejectionFromJson(json); + +@override final String clientMutationId; +@override final String code; +@override final String message; + +/// Create a copy of MutationRejection +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$MutationRejectionCopyWith<_MutationRejection> get copyWith => __$MutationRejectionCopyWithImpl<_MutationRejection>(this, _$identity); + +@override +Map toJson() { + return _$MutationRejectionToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _MutationRejection&&(identical(other.clientMutationId, clientMutationId) || other.clientMutationId == clientMutationId)&&(identical(other.code, code) || other.code == code)&&(identical(other.message, message) || other.message == message)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,clientMutationId,code,message); + +@override +String toString() { + return 'MutationRejection(clientMutationId: $clientMutationId, code: $code, message: $message)'; +} + + +} + +/// @nodoc +abstract mixin class _$MutationRejectionCopyWith<$Res> implements $MutationRejectionCopyWith<$Res> { + factory _$MutationRejectionCopyWith(_MutationRejection value, $Res Function(_MutationRejection) _then) = __$MutationRejectionCopyWithImpl; +@override @useResult +$Res call({ + String clientMutationId, String code, String message +}); + + + + +} +/// @nodoc +class __$MutationRejectionCopyWithImpl<$Res> + implements _$MutationRejectionCopyWith<$Res> { + __$MutationRejectionCopyWithImpl(this._self, this._then); + + final _MutationRejection _self; + final $Res Function(_MutationRejection) _then; + +/// Create a copy of MutationRejection +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? clientMutationId = null,Object? code = null,Object? message = null,}) { + return _then(_MutationRejection( +clientMutationId: null == clientMutationId ? _self.clientMutationId : clientMutationId // ignore: cast_nullable_to_non_nullable +as String,code: null == code ? _self.code : code // ignore: cast_nullable_to_non_nullable +as String,message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$SyncPushResult { + + String get cursor; List get accepted; List get rejected; +/// Create a copy of SyncPushResult +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SyncPushResultCopyWith get copyWith => _$SyncPushResultCopyWithImpl(this as SyncPushResult, _$identity); + + /// Serializes this SyncPushResult to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SyncPushResult&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other.accepted, accepted)&&const DeepCollectionEquality().equals(other.rejected, rejected)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cursor,const DeepCollectionEquality().hash(accepted),const DeepCollectionEquality().hash(rejected)); + +@override +String toString() { + return 'SyncPushResult(cursor: $cursor, accepted: $accepted, rejected: $rejected)'; +} + + +} + +/// @nodoc +abstract mixin class $SyncPushResultCopyWith<$Res> { + factory $SyncPushResultCopyWith(SyncPushResult value, $Res Function(SyncPushResult) _then) = _$SyncPushResultCopyWithImpl; +@useResult +$Res call({ + String cursor, List accepted, List rejected +}); + + + + +} +/// @nodoc +class _$SyncPushResultCopyWithImpl<$Res> + implements $SyncPushResultCopyWith<$Res> { + _$SyncPushResultCopyWithImpl(this._self, this._then); + + final SyncPushResult _self; + final $Res Function(SyncPushResult) _then; + +/// Create a copy of SyncPushResult +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? cursor = null,Object? accepted = null,Object? rejected = null,}) { + return _then(_self.copyWith( +cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String,accepted: null == accepted ? _self.accepted : accepted // ignore: cast_nullable_to_non_nullable +as List,rejected: null == rejected ? _self.rejected : rejected // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SyncPushResult]. +extension SyncPushResultPatterns on SyncPushResult { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SyncPushResult value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SyncPushResult() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SyncPushResult value) $default,){ +final _that = this; +switch (_that) { +case _SyncPushResult(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SyncPushResult value)? $default,){ +final _that = this; +switch (_that) { +case _SyncPushResult() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String cursor, List accepted, List rejected)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SyncPushResult() when $default != null: +return $default(_that.cursor,_that.accepted,_that.rejected);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String cursor, List accepted, List rejected) $default,) {final _that = this; +switch (_that) { +case _SyncPushResult(): +return $default(_that.cursor,_that.accepted,_that.rejected);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String cursor, List accepted, List rejected)? $default,) {final _that = this; +switch (_that) { +case _SyncPushResult() when $default != null: +return $default(_that.cursor,_that.accepted,_that.rejected);case _: + return null; + +} +} + +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _SyncPushResult implements SyncPushResult { + const _SyncPushResult({required this.cursor, final List accepted = const [], final List rejected = const []}): _accepted = accepted,_rejected = rejected; + factory _SyncPushResult.fromJson(Map json) => _$SyncPushResultFromJson(json); + +@override final String cursor; + final List _accepted; +@override@JsonKey() List get accepted { + if (_accepted is EqualUnmodifiableListView) return _accepted; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_accepted); +} + + final List _rejected; +@override@JsonKey() List get rejected { + if (_rejected is EqualUnmodifiableListView) return _rejected; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_rejected); +} + + +/// Create a copy of SyncPushResult +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SyncPushResultCopyWith<_SyncPushResult> get copyWith => __$SyncPushResultCopyWithImpl<_SyncPushResult>(this, _$identity); + +@override +Map toJson() { + return _$SyncPushResultToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SyncPushResult&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other._accepted, _accepted)&&const DeepCollectionEquality().equals(other._rejected, _rejected)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cursor,const DeepCollectionEquality().hash(_accepted),const DeepCollectionEquality().hash(_rejected)); + +@override +String toString() { + return 'SyncPushResult(cursor: $cursor, accepted: $accepted, rejected: $rejected)'; +} + + +} + +/// @nodoc +abstract mixin class _$SyncPushResultCopyWith<$Res> implements $SyncPushResultCopyWith<$Res> { + factory _$SyncPushResultCopyWith(_SyncPushResult value, $Res Function(_SyncPushResult) _then) = __$SyncPushResultCopyWithImpl; +@override @useResult +$Res call({ + String cursor, List accepted, List rejected +}); + + + + +} +/// @nodoc +class __$SyncPushResultCopyWithImpl<$Res> + implements _$SyncPushResultCopyWith<$Res> { + __$SyncPushResultCopyWithImpl(this._self, this._then); + + final _SyncPushResult _self; + final $Res Function(_SyncPushResult) _then; + +/// Create a copy of SyncPushResult +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? cursor = null,Object? accepted = null,Object? rejected = null,}) { + return _then(_SyncPushResult( +cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String,accepted: null == accepted ? _self._accepted : accepted // ignore: cast_nullable_to_non_nullable +as List,rejected: null == rejected ? _self._rejected : rejected // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$SyncPullResult { + + String get cursor; List get changes; +/// Create a copy of SyncPullResult +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SyncPullResultCopyWith get copyWith => _$SyncPullResultCopyWithImpl(this as SyncPullResult, _$identity); + + /// Serializes this SyncPullResult to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SyncPullResult&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other.changes, changes)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cursor,const DeepCollectionEquality().hash(changes)); + +@override +String toString() { + return 'SyncPullResult(cursor: $cursor, changes: $changes)'; +} + + +} + +/// @nodoc +abstract mixin class $SyncPullResultCopyWith<$Res> { + factory $SyncPullResultCopyWith(SyncPullResult value, $Res Function(SyncPullResult) _then) = _$SyncPullResultCopyWithImpl; +@useResult +$Res call({ + String cursor, List changes +}); + + + + +} +/// @nodoc +class _$SyncPullResultCopyWithImpl<$Res> + implements $SyncPullResultCopyWith<$Res> { + _$SyncPullResultCopyWithImpl(this._self, this._then); + + final SyncPullResult _self; + final $Res Function(SyncPullResult) _then; + +/// Create a copy of SyncPullResult +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? cursor = null,Object? changes = null,}) { + return _then(_self.copyWith( +cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String,changes: null == changes ? _self.changes : changes // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SyncPullResult]. +extension SyncPullResultPatterns on SyncPullResult { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SyncPullResult value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SyncPullResult() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SyncPullResult value) $default,){ +final _that = this; +switch (_that) { +case _SyncPullResult(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SyncPullResult value)? $default,){ +final _that = this; +switch (_that) { +case _SyncPullResult() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String cursor, List changes)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SyncPullResult() when $default != null: +return $default(_that.cursor,_that.changes);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String cursor, List changes) $default,) {final _that = this; +switch (_that) { +case _SyncPullResult(): +return $default(_that.cursor,_that.changes);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String cursor, List changes)? $default,) {final _that = this; +switch (_that) { +case _SyncPullResult() when $default != null: +return $default(_that.cursor,_that.changes);case _: + return null; + +} +} + +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _SyncPullResult implements SyncPullResult { + const _SyncPullResult({required this.cursor, final List changes = const []}): _changes = changes; + factory _SyncPullResult.fromJson(Map json) => _$SyncPullResultFromJson(json); + +@override final String cursor; + final List _changes; +@override@JsonKey() List get changes { + if (_changes is EqualUnmodifiableListView) return _changes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_changes); +} + + +/// Create a copy of SyncPullResult +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SyncPullResultCopyWith<_SyncPullResult> get copyWith => __$SyncPullResultCopyWithImpl<_SyncPullResult>(this, _$identity); + +@override +Map toJson() { + return _$SyncPullResultToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SyncPullResult&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other._changes, _changes)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cursor,const DeepCollectionEquality().hash(_changes)); + +@override +String toString() { + return 'SyncPullResult(cursor: $cursor, changes: $changes)'; +} + + +} + +/// @nodoc +abstract mixin class _$SyncPullResultCopyWith<$Res> implements $SyncPullResultCopyWith<$Res> { + factory _$SyncPullResultCopyWith(_SyncPullResult value, $Res Function(_SyncPullResult) _then) = __$SyncPullResultCopyWithImpl; +@override @useResult +$Res call({ + String cursor, List changes +}); + + + + +} +/// @nodoc +class __$SyncPullResultCopyWithImpl<$Res> + implements _$SyncPullResultCopyWith<$Res> { + __$SyncPullResultCopyWithImpl(this._self, this._then); + + final _SyncPullResult _self; + final $Res Function(_SyncPullResult) _then; + +/// Create a copy of SyncPullResult +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? cursor = null,Object? changes = null,}) { + return _then(_SyncPullResult( +cursor: null == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String,changes: null == changes ? _self._changes : changes // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$SignalItem { + + String get id; String get type; String get title; String? get description; String? get personId; DateTime get createdAt; Map? get metadata; +/// Create a copy of SignalItem +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SignalItemCopyWith get copyWith => _$SignalItemCopyWithImpl(this as SignalItem, _$identity); + + /// Serializes this SignalItem to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SignalItem&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.personId, personId) || other.personId == personId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&const DeepCollectionEquality().equals(other.metadata, metadata)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,type,title,description,personId,createdAt,const DeepCollectionEquality().hash(metadata)); + +@override +String toString() { + return 'SignalItem(id: $id, type: $type, title: $title, description: $description, personId: $personId, createdAt: $createdAt, metadata: $metadata)'; +} + + +} + +/// @nodoc +abstract mixin class $SignalItemCopyWith<$Res> { + factory $SignalItemCopyWith(SignalItem value, $Res Function(SignalItem) _then) = _$SignalItemCopyWithImpl; +@useResult +$Res call({ + String id, String type, String title, String? description, String? personId, DateTime createdAt, Map? metadata +}); + + + + +} +/// @nodoc +class _$SignalItemCopyWithImpl<$Res> + implements $SignalItemCopyWith<$Res> { + _$SignalItemCopyWithImpl(this._self, this._then); + + final SignalItem _self; + final $Res Function(SignalItem) _then; + +/// Create a copy of SignalItem +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? title = null,Object? description = freezed,Object? personId = freezed,Object? createdAt = null,Object? metadata = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,personId: freezed == personId ? _self.personId : personId // ignore: cast_nullable_to_non_nullable +as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,metadata: freezed == metadata ? _self.metadata : metadata // ignore: cast_nullable_to_non_nullable +as Map?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SignalItem]. +extension SignalItemPatterns on SignalItem { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SignalItem value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SignalItem() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SignalItem value) $default,){ +final _that = this; +switch (_that) { +case _SignalItem(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SignalItem value)? $default,){ +final _that = this; +switch (_that) { +case _SignalItem() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String type, String title, String? description, String? personId, DateTime createdAt, Map? metadata)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SignalItem() when $default != null: +return $default(_that.id,_that.type,_that.title,_that.description,_that.personId,_that.createdAt,_that.metadata);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String type, String title, String? description, String? personId, DateTime createdAt, Map? metadata) $default,) {final _that = this; +switch (_that) { +case _SignalItem(): +return $default(_that.id,_that.type,_that.title,_that.description,_that.personId,_that.createdAt,_that.metadata);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String type, String title, String? description, String? personId, DateTime createdAt, Map? metadata)? $default,) {final _that = this; +switch (_that) { +case _SignalItem() when $default != null: +return $default(_that.id,_that.type,_that.title,_that.description,_that.personId,_that.createdAt,_that.metadata);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SignalItem implements SignalItem { + const _SignalItem({required this.id, required this.type, required this.title, this.description, this.personId, required this.createdAt, final Map? metadata}): _metadata = metadata; + factory _SignalItem.fromJson(Map json) => _$SignalItemFromJson(json); + +@override final String id; +@override final String type; +@override final String title; +@override final String? description; +@override final String? personId; +@override final DateTime createdAt; + final Map? _metadata; +@override Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); +} + + +/// Create a copy of SignalItem +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SignalItemCopyWith<_SignalItem> get copyWith => __$SignalItemCopyWithImpl<_SignalItem>(this, _$identity); + +@override +Map toJson() { + return _$SignalItemToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SignalItem&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.personId, personId) || other.personId == personId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&const DeepCollectionEquality().equals(other._metadata, _metadata)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,type,title,description,personId,createdAt,const DeepCollectionEquality().hash(_metadata)); + +@override +String toString() { + return 'SignalItem(id: $id, type: $type, title: $title, description: $description, personId: $personId, createdAt: $createdAt, metadata: $metadata)'; +} + + +} + +/// @nodoc +abstract mixin class _$SignalItemCopyWith<$Res> implements $SignalItemCopyWith<$Res> { + factory _$SignalItemCopyWith(_SignalItem value, $Res Function(_SignalItem) _then) = __$SignalItemCopyWithImpl; +@override @useResult +$Res call({ + String id, String type, String title, String? description, String? personId, DateTime createdAt, Map? metadata +}); + + + + +} +/// @nodoc +class __$SignalItemCopyWithImpl<$Res> + implements _$SignalItemCopyWith<$Res> { + __$SignalItemCopyWithImpl(this._self, this._then); + + final _SignalItem _self; + final $Res Function(_SignalItem) _then; + +/// Create a copy of SignalItem +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? title = null,Object? description = freezed,Object? personId = freezed,Object? createdAt = null,Object? metadata = freezed,}) { + return _then(_SignalItem( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable +as String?,personId: freezed == personId ? _self.personId : personId // ignore: cast_nullable_to_non_nullable +as String?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,metadata: freezed == metadata ? _self._metadata : metadata // ignore: cast_nullable_to_non_nullable +as Map?, + )); +} + + +} + + +/// @nodoc +mixin _$SignalsFeed { + + String? get cursor; List get items; +/// Create a copy of SignalsFeed +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SignalsFeedCopyWith get copyWith => _$SignalsFeedCopyWithImpl(this as SignalsFeed, _$identity); + + /// Serializes this SignalsFeed to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SignalsFeed&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other.items, items)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cursor,const DeepCollectionEquality().hash(items)); + +@override +String toString() { + return 'SignalsFeed(cursor: $cursor, items: $items)'; +} + + +} + +/// @nodoc +abstract mixin class $SignalsFeedCopyWith<$Res> { + factory $SignalsFeedCopyWith(SignalsFeed value, $Res Function(SignalsFeed) _then) = _$SignalsFeedCopyWithImpl; +@useResult +$Res call({ + String? cursor, List items +}); + + + + +} +/// @nodoc +class _$SignalsFeedCopyWithImpl<$Res> + implements $SignalsFeedCopyWith<$Res> { + _$SignalsFeedCopyWithImpl(this._self, this._then); + + final SignalsFeed _self; + final $Res Function(SignalsFeed) _then; + +/// Create a copy of SignalsFeed +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? cursor = freezed,Object? items = null,}) { + return _then(_self.copyWith( +cursor: freezed == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String?,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SignalsFeed]. +extension SignalsFeedPatterns on SignalsFeed { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SignalsFeed value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SignalsFeed() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SignalsFeed value) $default,){ +final _that = this; +switch (_that) { +case _SignalsFeed(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SignalsFeed value)? $default,){ +final _that = this; +switch (_that) { +case _SignalsFeed() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? cursor, List items)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SignalsFeed() when $default != null: +return $default(_that.cursor,_that.items);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? cursor, List items) $default,) {final _that = this; +switch (_that) { +case _SignalsFeed(): +return $default(_that.cursor,_that.items);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? cursor, List items)? $default,) {final _that = this; +switch (_that) { +case _SignalsFeed() when $default != null: +return $default(_that.cursor,_that.items);case _: + return null; + +} +} + +} + +/// @nodoc + +@JsonSerializable(explicitToJson: true) +class _SignalsFeed implements SignalsFeed { + const _SignalsFeed({this.cursor, final List items = const []}): _items = items; + factory _SignalsFeed.fromJson(Map json) => _$SignalsFeedFromJson(json); + +@override final String? cursor; + final List _items; +@override@JsonKey() List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); +} + + +/// Create a copy of SignalsFeed +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SignalsFeedCopyWith<_SignalsFeed> get copyWith => __$SignalsFeedCopyWithImpl<_SignalsFeed>(this, _$identity); + +@override +Map toJson() { + return _$SignalsFeedToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SignalsFeed&&(identical(other.cursor, cursor) || other.cursor == cursor)&&const DeepCollectionEquality().equals(other._items, _items)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,cursor,const DeepCollectionEquality().hash(_items)); + +@override +String toString() { + return 'SignalsFeed(cursor: $cursor, items: $items)'; +} + + +} + +/// @nodoc +abstract mixin class _$SignalsFeedCopyWith<$Res> implements $SignalsFeedCopyWith<$Res> { + factory _$SignalsFeedCopyWith(_SignalsFeed value, $Res Function(_SignalsFeed) _then) = __$SignalsFeedCopyWithImpl; +@override @useResult +$Res call({ + String? cursor, List items +}); + + + + +} +/// @nodoc +class __$SignalsFeedCopyWithImpl<$Res> + implements _$SignalsFeedCopyWith<$Res> { + __$SignalsFeedCopyWithImpl(this._self, this._then); + + final _SignalsFeed _self; + final $Res Function(_SignalsFeed) _then; + +/// Create a copy of SignalsFeed +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? cursor = freezed,Object? items = null,}) { + return _then(_SignalsFeed( +cursor: freezed == cursor ? _self.cursor : cursor // ignore: cast_nullable_to_non_nullable +as String?,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$SignalAckRequest { + + SignalAction get action; DateTime get at; +/// Create a copy of SignalAckRequest +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$SignalAckRequestCopyWith get copyWith => _$SignalAckRequestCopyWithImpl(this as SignalAckRequest, _$identity); + + /// Serializes this SignalAckRequest to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is SignalAckRequest&&(identical(other.action, action) || other.action == action)&&(identical(other.at, at) || other.at == at)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,action,at); + +@override +String toString() { + return 'SignalAckRequest(action: $action, at: $at)'; +} + + +} + +/// @nodoc +abstract mixin class $SignalAckRequestCopyWith<$Res> { + factory $SignalAckRequestCopyWith(SignalAckRequest value, $Res Function(SignalAckRequest) _then) = _$SignalAckRequestCopyWithImpl; +@useResult +$Res call({ + SignalAction action, DateTime at +}); + + + + +} +/// @nodoc +class _$SignalAckRequestCopyWithImpl<$Res> + implements $SignalAckRequestCopyWith<$Res> { + _$SignalAckRequestCopyWithImpl(this._self, this._then); + + final SignalAckRequest _self; + final $Res Function(SignalAckRequest) _then; + +/// Create a copy of SignalAckRequest +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? action = null,Object? at = null,}) { + return _then(_self.copyWith( +action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable +as SignalAction,at: null == at ? _self.at : at // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + +} + + +/// Adds pattern-matching-related methods to [SignalAckRequest]. +extension SignalAckRequestPatterns on SignalAckRequest { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _SignalAckRequest value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _SignalAckRequest() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _SignalAckRequest value) $default,){ +final _that = this; +switch (_that) { +case _SignalAckRequest(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _SignalAckRequest value)? $default,){ +final _that = this; +switch (_that) { +case _SignalAckRequest() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( SignalAction action, DateTime at)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _SignalAckRequest() when $default != null: +return $default(_that.action,_that.at);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( SignalAction action, DateTime at) $default,) {final _that = this; +switch (_that) { +case _SignalAckRequest(): +return $default(_that.action,_that.at);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( SignalAction action, DateTime at)? $default,) {final _that = this; +switch (_that) { +case _SignalAckRequest() when $default != null: +return $default(_that.action,_that.at);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _SignalAckRequest implements SignalAckRequest { + const _SignalAckRequest({required this.action, required this.at}); + factory _SignalAckRequest.fromJson(Map json) => _$SignalAckRequestFromJson(json); + +@override final SignalAction action; +@override final DateTime at; + +/// Create a copy of SignalAckRequest +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$SignalAckRequestCopyWith<_SignalAckRequest> get copyWith => __$SignalAckRequestCopyWithImpl<_SignalAckRequest>(this, _$identity); + +@override +Map toJson() { + return _$SignalAckRequestToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _SignalAckRequest&&(identical(other.action, action) || other.action == action)&&(identical(other.at, at) || other.at == at)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,action,at); + +@override +String toString() { + return 'SignalAckRequest(action: $action, at: $at)'; +} + + +} + +/// @nodoc +abstract mixin class _$SignalAckRequestCopyWith<$Res> implements $SignalAckRequestCopyWith<$Res> { + factory _$SignalAckRequestCopyWith(_SignalAckRequest value, $Res Function(_SignalAckRequest) _then) = __$SignalAckRequestCopyWithImpl; +@override @useResult +$Res call({ + SignalAction action, DateTime at +}); + + + + +} +/// @nodoc +class __$SignalAckRequestCopyWithImpl<$Res> + implements _$SignalAckRequestCopyWith<$Res> { + __$SignalAckRequestCopyWithImpl(this._self, this._then); + + final _SignalAckRequest _self; + final $Res Function(_SignalAckRequest) _then; + +/// Create a copy of SignalAckRequest +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? action = null,Object? at = null,}) { + return _then(_SignalAckRequest( +action: null == action ? _self.action : action // ignore: cast_nullable_to_non_nullable +as SignalAction,at: null == at ? _self.at : at // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + + +} + + +/// @nodoc +mixin _$UploadInitRequest { + + UploadPurpose get purpose; String get contentType; int get sizeBytes; +/// Create a copy of UploadInitRequest +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$UploadInitRequestCopyWith get copyWith => _$UploadInitRequestCopyWithImpl(this as UploadInitRequest, _$identity); + + /// Serializes this UploadInitRequest to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is UploadInitRequest&&(identical(other.purpose, purpose) || other.purpose == purpose)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.sizeBytes, sizeBytes) || other.sizeBytes == sizeBytes)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,purpose,contentType,sizeBytes); + +@override +String toString() { + return 'UploadInitRequest(purpose: $purpose, contentType: $contentType, sizeBytes: $sizeBytes)'; +} + + +} + +/// @nodoc +abstract mixin class $UploadInitRequestCopyWith<$Res> { + factory $UploadInitRequestCopyWith(UploadInitRequest value, $Res Function(UploadInitRequest) _then) = _$UploadInitRequestCopyWithImpl; +@useResult +$Res call({ + UploadPurpose purpose, String contentType, int sizeBytes +}); + + + + +} +/// @nodoc +class _$UploadInitRequestCopyWithImpl<$Res> + implements $UploadInitRequestCopyWith<$Res> { + _$UploadInitRequestCopyWithImpl(this._self, this._then); + + final UploadInitRequest _self; + final $Res Function(UploadInitRequest) _then; + +/// Create a copy of UploadInitRequest +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? purpose = null,Object? contentType = null,Object? sizeBytes = null,}) { + return _then(_self.copyWith( +purpose: null == purpose ? _self.purpose : purpose // ignore: cast_nullable_to_non_nullable +as UploadPurpose,contentType: null == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable +as String,sizeBytes: null == sizeBytes ? _self.sizeBytes : sizeBytes // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [UploadInitRequest]. +extension UploadInitRequestPatterns on UploadInitRequest { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _UploadInitRequest value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _UploadInitRequest() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _UploadInitRequest value) $default,){ +final _that = this; +switch (_that) { +case _UploadInitRequest(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _UploadInitRequest value)? $default,){ +final _that = this; +switch (_that) { +case _UploadInitRequest() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( UploadPurpose purpose, String contentType, int sizeBytes)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _UploadInitRequest() when $default != null: +return $default(_that.purpose,_that.contentType,_that.sizeBytes);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( UploadPurpose purpose, String contentType, int sizeBytes) $default,) {final _that = this; +switch (_that) { +case _UploadInitRequest(): +return $default(_that.purpose,_that.contentType,_that.sizeBytes);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( UploadPurpose purpose, String contentType, int sizeBytes)? $default,) {final _that = this; +switch (_that) { +case _UploadInitRequest() when $default != null: +return $default(_that.purpose,_that.contentType,_that.sizeBytes);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _UploadInitRequest implements UploadInitRequest { + const _UploadInitRequest({required this.purpose, required this.contentType, required this.sizeBytes}); + factory _UploadInitRequest.fromJson(Map json) => _$UploadInitRequestFromJson(json); + +@override final UploadPurpose purpose; +@override final String contentType; +@override final int sizeBytes; + +/// Create a copy of UploadInitRequest +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$UploadInitRequestCopyWith<_UploadInitRequest> get copyWith => __$UploadInitRequestCopyWithImpl<_UploadInitRequest>(this, _$identity); + +@override +Map toJson() { + return _$UploadInitRequestToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _UploadInitRequest&&(identical(other.purpose, purpose) || other.purpose == purpose)&&(identical(other.contentType, contentType) || other.contentType == contentType)&&(identical(other.sizeBytes, sizeBytes) || other.sizeBytes == sizeBytes)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,purpose,contentType,sizeBytes); + +@override +String toString() { + return 'UploadInitRequest(purpose: $purpose, contentType: $contentType, sizeBytes: $sizeBytes)'; +} + + +} + +/// @nodoc +abstract mixin class _$UploadInitRequestCopyWith<$Res> implements $UploadInitRequestCopyWith<$Res> { + factory _$UploadInitRequestCopyWith(_UploadInitRequest value, $Res Function(_UploadInitRequest) _then) = __$UploadInitRequestCopyWithImpl; +@override @useResult +$Res call({ + UploadPurpose purpose, String contentType, int sizeBytes +}); + + + + +} +/// @nodoc +class __$UploadInitRequestCopyWithImpl<$Res> + implements _$UploadInitRequestCopyWith<$Res> { + __$UploadInitRequestCopyWithImpl(this._self, this._then); + + final _UploadInitRequest _self; + final $Res Function(_UploadInitRequest) _then; + +/// Create a copy of UploadInitRequest +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? purpose = null,Object? contentType = null,Object? sizeBytes = null,}) { + return _then(_UploadInitRequest( +purpose: null == purpose ? _self.purpose : purpose // ignore: cast_nullable_to_non_nullable +as UploadPurpose,contentType: null == contentType ? _self.contentType : contentType // ignore: cast_nullable_to_non_nullable +as String,sizeBytes: null == sizeBytes ? _self.sizeBytes : sizeBytes // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + + +/// @nodoc +mixin _$UploadInitResult { + + String get uploadUrl; String get fileId; DateTime get expiresAt; +/// Create a copy of UploadInitResult +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$UploadInitResultCopyWith get copyWith => _$UploadInitResultCopyWithImpl(this as UploadInitResult, _$identity); + + /// Serializes this UploadInitResult to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is UploadInitResult&&(identical(other.uploadUrl, uploadUrl) || other.uploadUrl == uploadUrl)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,uploadUrl,fileId,expiresAt); + +@override +String toString() { + return 'UploadInitResult(uploadUrl: $uploadUrl, fileId: $fileId, expiresAt: $expiresAt)'; +} + + +} + +/// @nodoc +abstract mixin class $UploadInitResultCopyWith<$Res> { + factory $UploadInitResultCopyWith(UploadInitResult value, $Res Function(UploadInitResult) _then) = _$UploadInitResultCopyWithImpl; +@useResult +$Res call({ + String uploadUrl, String fileId, DateTime expiresAt +}); + + + + +} +/// @nodoc +class _$UploadInitResultCopyWithImpl<$Res> + implements $UploadInitResultCopyWith<$Res> { + _$UploadInitResultCopyWithImpl(this._self, this._then); + + final UploadInitResult _self; + final $Res Function(UploadInitResult) _then; + +/// Create a copy of UploadInitResult +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? uploadUrl = null,Object? fileId = null,Object? expiresAt = null,}) { + return _then(_self.copyWith( +uploadUrl: null == uploadUrl ? _self.uploadUrl : uploadUrl // ignore: cast_nullable_to_non_nullable +as String,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullable_to_non_nullable +as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + +} + + +/// Adds pattern-matching-related methods to [UploadInitResult]. +extension UploadInitResultPatterns on UploadInitResult { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _UploadInitResult value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _UploadInitResult() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _UploadInitResult value) $default,){ +final _that = this; +switch (_that) { +case _UploadInitResult(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _UploadInitResult value)? $default,){ +final _that = this; +switch (_that) { +case _UploadInitResult() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String uploadUrl, String fileId, DateTime expiresAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _UploadInitResult() when $default != null: +return $default(_that.uploadUrl,_that.fileId,_that.expiresAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String uploadUrl, String fileId, DateTime expiresAt) $default,) {final _that = this; +switch (_that) { +case _UploadInitResult(): +return $default(_that.uploadUrl,_that.fileId,_that.expiresAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String uploadUrl, String fileId, DateTime expiresAt)? $default,) {final _that = this; +switch (_that) { +case _UploadInitResult() when $default != null: +return $default(_that.uploadUrl,_that.fileId,_that.expiresAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _UploadInitResult implements UploadInitResult { + const _UploadInitResult({required this.uploadUrl, required this.fileId, required this.expiresAt}); + factory _UploadInitResult.fromJson(Map json) => _$UploadInitResultFromJson(json); + +@override final String uploadUrl; +@override final String fileId; +@override final DateTime expiresAt; + +/// Create a copy of UploadInitResult +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$UploadInitResultCopyWith<_UploadInitResult> get copyWith => __$UploadInitResultCopyWithImpl<_UploadInitResult>(this, _$identity); + +@override +Map toJson() { + return _$UploadInitResultToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _UploadInitResult&&(identical(other.uploadUrl, uploadUrl) || other.uploadUrl == uploadUrl)&&(identical(other.fileId, fileId) || other.fileId == fileId)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,uploadUrl,fileId,expiresAt); + +@override +String toString() { + return 'UploadInitResult(uploadUrl: $uploadUrl, fileId: $fileId, expiresAt: $expiresAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$UploadInitResultCopyWith<$Res> implements $UploadInitResultCopyWith<$Res> { + factory _$UploadInitResultCopyWith(_UploadInitResult value, $Res Function(_UploadInitResult) _then) = __$UploadInitResultCopyWithImpl; +@override @useResult +$Res call({ + String uploadUrl, String fileId, DateTime expiresAt +}); + + + + +} +/// @nodoc +class __$UploadInitResultCopyWithImpl<$Res> + implements _$UploadInitResultCopyWith<$Res> { + __$UploadInitResultCopyWithImpl(this._self, this._then); + + final _UploadInitResult _self; + final $Res Function(_UploadInitResult) _then; + +/// Create a copy of UploadInitResult +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? uploadUrl = null,Object? fileId = null,Object? expiresAt = null,}) { + return _then(_UploadInitResult( +uploadUrl: null == uploadUrl ? _self.uploadUrl : uploadUrl // ignore: cast_nullable_to_non_nullable +as String,fileId: null == fileId ? _self.fileId : fileId // ignore: cast_nullable_to_non_nullable +as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + + +} + + +/// @nodoc +mixin _$DownloadUrl { + + String get downloadUrl; DateTime get expiresAt; +/// Create a copy of DownloadUrl +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$DownloadUrlCopyWith get copyWith => _$DownloadUrlCopyWithImpl(this as DownloadUrl, _$identity); + + /// Serializes this DownloadUrl to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is DownloadUrl&&(identical(other.downloadUrl, downloadUrl) || other.downloadUrl == downloadUrl)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,downloadUrl,expiresAt); + +@override +String toString() { + return 'DownloadUrl(downloadUrl: $downloadUrl, expiresAt: $expiresAt)'; +} + + +} + +/// @nodoc +abstract mixin class $DownloadUrlCopyWith<$Res> { + factory $DownloadUrlCopyWith(DownloadUrl value, $Res Function(DownloadUrl) _then) = _$DownloadUrlCopyWithImpl; +@useResult +$Res call({ + String downloadUrl, DateTime expiresAt +}); + + + + +} +/// @nodoc +class _$DownloadUrlCopyWithImpl<$Res> + implements $DownloadUrlCopyWith<$Res> { + _$DownloadUrlCopyWithImpl(this._self, this._then); + + final DownloadUrl _self; + final $Res Function(DownloadUrl) _then; + +/// Create a copy of DownloadUrl +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? downloadUrl = null,Object? expiresAt = null,}) { + return _then(_self.copyWith( +downloadUrl: null == downloadUrl ? _self.downloadUrl : downloadUrl // ignore: cast_nullable_to_non_nullable +as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + +} + + +/// Adds pattern-matching-related methods to [DownloadUrl]. +extension DownloadUrlPatterns on DownloadUrl { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _DownloadUrl value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _DownloadUrl() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _DownloadUrl value) $default,){ +final _that = this; +switch (_that) { +case _DownloadUrl(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _DownloadUrl value)? $default,){ +final _that = this; +switch (_that) { +case _DownloadUrl() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String downloadUrl, DateTime expiresAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _DownloadUrl() when $default != null: +return $default(_that.downloadUrl,_that.expiresAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String downloadUrl, DateTime expiresAt) $default,) {final _that = this; +switch (_that) { +case _DownloadUrl(): +return $default(_that.downloadUrl,_that.expiresAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String downloadUrl, DateTime expiresAt)? $default,) {final _that = this; +switch (_that) { +case _DownloadUrl() when $default != null: +return $default(_that.downloadUrl,_that.expiresAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _DownloadUrl implements DownloadUrl { + const _DownloadUrl({required this.downloadUrl, required this.expiresAt}); + factory _DownloadUrl.fromJson(Map json) => _$DownloadUrlFromJson(json); + +@override final String downloadUrl; +@override final DateTime expiresAt; + +/// Create a copy of DownloadUrl +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$DownloadUrlCopyWith<_DownloadUrl> get copyWith => __$DownloadUrlCopyWithImpl<_DownloadUrl>(this, _$identity); + +@override +Map toJson() { + return _$DownloadUrlToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _DownloadUrl&&(identical(other.downloadUrl, downloadUrl) || other.downloadUrl == downloadUrl)&&(identical(other.expiresAt, expiresAt) || other.expiresAt == expiresAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,downloadUrl,expiresAt); + +@override +String toString() { + return 'DownloadUrl(downloadUrl: $downloadUrl, expiresAt: $expiresAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$DownloadUrlCopyWith<$Res> implements $DownloadUrlCopyWith<$Res> { + factory _$DownloadUrlCopyWith(_DownloadUrl value, $Res Function(_DownloadUrl) _then) = __$DownloadUrlCopyWithImpl; +@override @useResult +$Res call({ + String downloadUrl, DateTime expiresAt +}); + + + + +} +/// @nodoc +class __$DownloadUrlCopyWithImpl<$Res> + implements _$DownloadUrlCopyWith<$Res> { + __$DownloadUrlCopyWithImpl(this._self, this._then); + + final _DownloadUrl _self; + final $Res Function(_DownloadUrl) _then; + +/// Create a copy of DownloadUrl +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? downloadUrl = null,Object? expiresAt = null,}) { + return _then(_DownloadUrl( +downloadUrl: null == downloadUrl ? _self.downloadUrl : downloadUrl // ignore: cast_nullable_to_non_nullable +as String,expiresAt: null == expiresAt ? _self.expiresAt : expiresAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + + +} + +// dart format on diff --git a/lib/integrations/backend/models/backend_models.g.dart b/lib/integrations/backend/models/backend_models.g.dart new file mode 100644 index 0000000..9a03ee7 --- /dev/null +++ b/lib/integrations/backend/models/backend_models.g.dart @@ -0,0 +1,293 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'backend_models.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_SignInRequest _$SignInRequestFromJson(Map json) => + _SignInRequest( + method: $enumDecode(_$SignInMethodEnumMap, json['method']), + email: json['email'] as String?, + password: json['password'] as String?, + idToken: json['idToken'] as String?, + ); + +Map _$SignInRequestToJson(_SignInRequest instance) => + { + 'method': _$SignInMethodEnumMap[instance.method]!, + 'email': instance.email, + 'password': instance.password, + 'idToken': instance.idToken, + }; + +const _$SignInMethodEnumMap = { + SignInMethod.emailMagicLink: 'email_magic_link', + SignInMethod.password: 'password', + SignInMethod.oidc: 'oidc', +}; + +_UserProfile _$UserProfileFromJson(Map json) => _UserProfile( + id: json['id'] as String, + email: json['email'] as String?, + displayName: json['displayName'] as String?, + avatarFileId: json['avatarFileId'] as String?, +); + +Map _$UserProfileToJson(_UserProfile instance) => + { + 'id': instance.id, + 'email': instance.email, + 'displayName': instance.displayName, + 'avatarFileId': instance.avatarFileId, + }; + +_AuthSession _$AuthSessionFromJson(Map json) => _AuthSession( + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] as String, + expiresAt: DateTime.parse(json['expiresAt'] as String), + user: UserProfile.fromJson(json['user'] as Map), +); + +Map _$AuthSessionToJson(_AuthSession instance) => + { + 'accessToken': instance.accessToken, + 'refreshToken': instance.refreshToken, + 'expiresAt': instance.expiresAt.toIso8601String(), + 'user': instance.user.toJson(), + }; + +_AuthRefreshRequest _$AuthRefreshRequestFromJson(Map json) => + _AuthRefreshRequest(refreshToken: json['refreshToken'] as String); + +Map _$AuthRefreshRequestToJson(_AuthRefreshRequest instance) => + {'refreshToken': instance.refreshToken}; + +_AuthRefreshResponse _$AuthRefreshResponseFromJson(Map json) => + _AuthRefreshResponse( + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] as String?, + expiresAt: DateTime.parse(json['expiresAt'] as String), + ); + +Map _$AuthRefreshResponseToJson( + _AuthRefreshResponse instance, +) => { + 'accessToken': instance.accessToken, + 'refreshToken': instance.refreshToken, + 'expiresAt': instance.expiresAt.toIso8601String(), +}; + +_ChangeEnvelope _$ChangeEnvelopeFromJson(Map json) => + _ChangeEnvelope( + schemaVersion: (json['schemaVersion'] as num).toInt(), + entityType: json['entityType'] as String, + entityId: json['entityId'] as String, + op: $enumDecode(_$ChangeOperationEnumMap, json['op']), + modifiedAt: DateTime.parse(json['modifiedAt'] as String), + clientMutationId: json['clientMutationId'] as String, + payload: json['payload'] as Map?, + ); + +Map _$ChangeEnvelopeToJson(_ChangeEnvelope instance) => + { + 'schemaVersion': instance.schemaVersion, + 'entityType': instance.entityType, + 'entityId': instance.entityId, + 'op': _$ChangeOperationEnumMap[instance.op]!, + 'modifiedAt': instance.modifiedAt.toIso8601String(), + 'clientMutationId': instance.clientMutationId, + 'payload': instance.payload, + }; + +const _$ChangeOperationEnumMap = { + ChangeOperation.upsert: 'upsert', + ChangeOperation.delete: 'delete', +}; + +_SyncPushRequest _$SyncPushRequestFromJson(Map json) => + _SyncPushRequest( + deviceId: json['deviceId'] as String, + cursor: json['cursor'] as String?, + changes: (json['changes'] as List) + .map((e) => ChangeEnvelope.fromJson(e as Map)) + .toList(), + idempotencyKey: json['idempotencyKey'] as String, + ); + +Map _$SyncPushRequestToJson(_SyncPushRequest instance) => + { + 'deviceId': instance.deviceId, + 'cursor': instance.cursor, + 'changes': instance.changes.map((e) => e.toJson()).toList(), + 'idempotencyKey': instance.idempotencyKey, + }; + +_MutationAck _$MutationAckFromJson(Map json) => _MutationAck( + clientMutationId: json['clientMutationId'] as String, + serverMutationId: json['serverMutationId'] as String?, + acceptedAt: json['acceptedAt'] == null + ? null + : DateTime.parse(json['acceptedAt'] as String), +); + +Map _$MutationAckToJson(_MutationAck instance) => + { + 'clientMutationId': instance.clientMutationId, + 'serverMutationId': instance.serverMutationId, + 'acceptedAt': instance.acceptedAt?.toIso8601String(), + }; + +_MutationRejection _$MutationRejectionFromJson(Map json) => + _MutationRejection( + clientMutationId: json['clientMutationId'] as String, + code: json['code'] as String, + message: json['message'] as String, + ); + +Map _$MutationRejectionToJson(_MutationRejection instance) => + { + 'clientMutationId': instance.clientMutationId, + 'code': instance.code, + 'message': instance.message, + }; + +_SyncPushResult _$SyncPushResultFromJson(Map json) => + _SyncPushResult( + cursor: json['cursor'] as String, + accepted: + (json['accepted'] as List?) + ?.map((e) => MutationAck.fromJson(e as Map)) + .toList() ?? + const [], + rejected: + (json['rejected'] as List?) + ?.map( + (e) => MutationRejection.fromJson(e as Map), + ) + .toList() ?? + const [], + ); + +Map _$SyncPushResultToJson(_SyncPushResult instance) => + { + 'cursor': instance.cursor, + 'accepted': instance.accepted.map((e) => e.toJson()).toList(), + 'rejected': instance.rejected.map((e) => e.toJson()).toList(), + }; + +_SyncPullResult _$SyncPullResultFromJson(Map json) => + _SyncPullResult( + cursor: json['cursor'] as String, + changes: + (json['changes'] as List?) + ?.map((e) => ChangeEnvelope.fromJson(e as Map)) + .toList() ?? + const [], + ); + +Map _$SyncPullResultToJson(_SyncPullResult instance) => + { + 'cursor': instance.cursor, + 'changes': instance.changes.map((e) => e.toJson()).toList(), + }; + +_SignalItem _$SignalItemFromJson(Map json) => _SignalItem( + id: json['id'] as String, + type: json['type'] as String, + title: json['title'] as String, + description: json['description'] as String?, + personId: json['personId'] as String?, + createdAt: DateTime.parse(json['createdAt'] as String), + metadata: json['metadata'] as Map?, +); + +Map _$SignalItemToJson(_SignalItem instance) => + { + 'id': instance.id, + 'type': instance.type, + 'title': instance.title, + 'description': instance.description, + 'personId': instance.personId, + 'createdAt': instance.createdAt.toIso8601String(), + 'metadata': instance.metadata, + }; + +_SignalsFeed _$SignalsFeedFromJson(Map json) => _SignalsFeed( + cursor: json['cursor'] as String?, + items: + (json['items'] as List?) + ?.map((e) => SignalItem.fromJson(e as Map)) + .toList() ?? + const [], +); + +Map _$SignalsFeedToJson(_SignalsFeed instance) => + { + 'cursor': instance.cursor, + 'items': instance.items.map((e) => e.toJson()).toList(), + }; + +_SignalAckRequest _$SignalAckRequestFromJson(Map json) => + _SignalAckRequest( + action: $enumDecode(_$SignalActionEnumMap, json['action']), + at: DateTime.parse(json['at'] as String), + ); + +Map _$SignalAckRequestToJson(_SignalAckRequest instance) => + { + 'action': _$SignalActionEnumMap[instance.action]!, + 'at': instance.at.toIso8601String(), + }; + +const _$SignalActionEnumMap = { + SignalAction.viewed: 'viewed', + SignalAction.dismissed: 'dismissed', + SignalAction.saved: 'saved', + SignalAction.purchased: 'purchased', +}; + +_UploadInitRequest _$UploadInitRequestFromJson(Map json) => + _UploadInitRequest( + purpose: $enumDecode(_$UploadPurposeEnumMap, json['purpose']), + contentType: json['contentType'] as String, + sizeBytes: (json['sizeBytes'] as num).toInt(), + ); + +Map _$UploadInitRequestToJson(_UploadInitRequest instance) => + { + 'purpose': _$UploadPurposeEnumMap[instance.purpose]!, + 'contentType': instance.contentType, + 'sizeBytes': instance.sizeBytes, + }; + +const _$UploadPurposeEnumMap = { + UploadPurpose.avatar: 'avatar', + UploadPurpose.attachment: 'attachment', +}; + +_UploadInitResult _$UploadInitResultFromJson(Map json) => + _UploadInitResult( + uploadUrl: json['uploadUrl'] as String, + fileId: json['fileId'] as String, + expiresAt: DateTime.parse(json['expiresAt'] as String), + ); + +Map _$UploadInitResultToJson(_UploadInitResult instance) => + { + 'uploadUrl': instance.uploadUrl, + 'fileId': instance.fileId, + 'expiresAt': instance.expiresAt.toIso8601String(), + }; + +_DownloadUrl _$DownloadUrlFromJson(Map json) => _DownloadUrl( + downloadUrl: json['downloadUrl'] as String, + expiresAt: DateTime.parse(json['expiresAt'] as String), +); + +Map _$DownloadUrlToJson(_DownloadUrl instance) => + { + 'downloadUrl': instance.downloadUrl, + 'expiresAt': instance.expiresAt.toIso8601String(), + }; diff --git a/lib/integrations/backend/sync_envelope_validator.dart b/lib/integrations/backend/sync_envelope_validator.dart new file mode 100644 index 0000000..bc1b05b --- /dev/null +++ b/lib/integrations/backend/sync_envelope_validator.dart @@ -0,0 +1,35 @@ +import 'package:relationship_saver/core/network/backend_exception.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +/// Validation rules for outgoing sync envelopes. +class SyncEnvelopeValidator { + SyncEnvelopeValidator._(); + + /// Validates a list of changes before a push call. + static void validateAll(List changes) { + for (final ChangeEnvelope change in changes) { + validate(change); + } + } + + /// Validates one change envelope. + static void validate(ChangeEnvelope change) { + if (change.schemaVersion <= 0) { + throw ValidationException( + 'schemaVersion must be > 0 for ${change.clientMutationId}', + ); + } + + if (change.op == ChangeOperation.delete && change.payload != null) { + throw ValidationException( + 'payload must be null for delete operation (${change.clientMutationId})', + ); + } + + if (change.op == ChangeOperation.upsert && change.payload == null) { + throw ValidationException( + 'payload is required for upsert operation (${change.clientMutationId})', + ); + } + } +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..faea750 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:relationship_saver/core/config/app_config.dart'; +import 'package:relationship_saver/core/config/app_theme.dart'; + +void main() { + runApp(const ProviderScope(child: RelationshipSaverApp())); +} + +class RelationshipSaverApp extends StatelessWidget { + const RelationshipSaverApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Relationship Saver', + debugShowCheckedModeBanner: false, + theme: AppTheme.light(), + home: const _LandingScreen(), + ); + } +} + +class _LandingScreen extends StatelessWidget { + const _LandingScreen(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Relationship Saver')), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Backend gateway scaffold is ready.', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 12), + Text( + 'Mode: ${AppConfig.useFakeBackend ? 'Fake gateway' : 'REST gateway'}', + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 8), + Text( + 'Base URL: ${AppConfig.backendBaseUrl}', + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + ); + } +} diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..9e4a305 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "relationship_saver") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.relationship_saver") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..d0e7f79 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..b29e9ba --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 0000000..064026e --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "relationship_saver"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "relationship_saver"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..67d652c --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import flutter_secure_storage_darwin + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) +} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/macos/Podfile.lock b/macos/Podfile.lock new file mode 100644 index 0000000..d2d3b17 --- /dev/null +++ b/macos/Podfile.lock @@ -0,0 +1,23 @@ +PODS: + - flutter_secure_storage_darwin (10.0.0): + - Flutter + - FlutterMacOS + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - flutter_secure_storage_darwin (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin`) + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + flutter_secure_storage_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_darwin/darwin + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a56ab99 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 4513571BB301E4F5C81114B9 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C675E818994A33CE10D09857 /* Pods_RunnerTests.framework */; }; + F9E74E370A789E2E57287850 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11B8F7B6498AACF12FF53C73 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 11B8F7B6498AACF12FF53C73 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* relationship_saver.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = relationship_saver.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 3D00D3F9CCA4614EFCD8E5C3 /* 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 = ""; }; + 532177A58D5EB6791C0995B7 /* 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 = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + A5F3E60CE4818B765D19A2C8 /* 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 = ""; }; + ABCA90738EB6ACAA750EE1F0 /* 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 = ""; }; + C675E818994A33CE10D09857 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D5AC6930A761282D2173653E /* 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 = ""; }; + EE4703EC53C604E886AD80CD /* 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4513571BB301E4F5C81114B9 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F9E74E370A789E2E57287850 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + BDE3744CBB52080CC8DA31F5 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* relationship_saver.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + BDE3744CBB52080CC8DA31F5 /* Pods */ = { + isa = PBXGroup; + children = ( + A5F3E60CE4818B765D19A2C8 /* Pods-Runner.debug.xcconfig */, + ABCA90738EB6ACAA750EE1F0 /* Pods-Runner.release.xcconfig */, + 532177A58D5EB6791C0995B7 /* Pods-Runner.profile.xcconfig */, + D5AC6930A761282D2173653E /* Pods-RunnerTests.debug.xcconfig */, + EE4703EC53C604E886AD80CD /* Pods-RunnerTests.release.xcconfig */, + 3D00D3F9CCA4614EFCD8E5C3 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 11B8F7B6498AACF12FF53C73 /* Pods_Runner.framework */, + C675E818994A33CE10D09857 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 19C327B170757D4960A3CBE0 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 90C6C75FF5C0992FA3B20806 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + CB2149DF1950F14B4329E97F /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* relationship_saver.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 19C327B170757D4960A3CBE0 /* [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; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 90C6C75FF5C0992FA3B20806 /* [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; + }; + CB2149DF1950F14B4329E97F /* [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; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D5AC6930A761282D2173653E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/relationship_saver.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/relationship_saver"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EE4703EC53C604E886AD80CD /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/relationship_saver.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/relationship_saver"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3D00D3F9CCA4614EFCD8E5C3 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/relationship_saver.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/relationship_saver"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e6783df --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..24bc69c --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = relationship_saver + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.relationshipSaver + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..f6ebf0a --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,906 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d" + url: "https://pub.dev" + source: hosted + version: "93.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b + url: "https://pub.dev" + source: hosted + version: "10.0.1" + 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" + url: "https://pub.dev" + source: hosted + version: "2.13.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: "275bf6bb2a00a9852c28d4e0b410da1d833a734d57d39d44f94bfc895a484ec3" + url: "https://pub.dev" + source: hosted + version: "4.0.4" + 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: "39ad4ca8a2876779737c60e4228b4bcd35d4352ef7e14e47514093edc012c734" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" + url: "https://pub.dev" + source: hosted + version: "8.12.3" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + clock: + dependency: "direct main" + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + 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" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "15a7db352c8fc6a4d2bc475ba901c25b39fe7157541da4c16eacce6f8be83e49" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + dio: + dependency: "direct main" + description: + name: dio + sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 + url: "https://pub.dev" + source: hosted + version: "5.9.1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + 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: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: e2026c72738a925a60db30258ff1f29974e40716749f3c9850aabf34ffc1a14c + url: "https://pub.dev" + source: hosted + version: "3.2.1" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 + url: "https://pub.dev" + source: hosted + version: "10.0.0" + flutter_secure_storage_darwin: + dependency: transitive + description: + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: f23ea33b3863f119b58ed1b586e881a46bd28715ddcc4dbc33104524e3434131 + url: "https://pub.dev" + source: hosted + version: "3.2.5" + 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" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hooks: + dependency: transitive + description: + name: hooks + sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + 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" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "805fa86df56383000f640384b282ce0cb8431f1a7a2396de92fb66186d8c57df" + url: "https://pub.dev" + source: hosted + version: "4.10.0" + json_schema: + dependency: transitive + description: + name: json_schema + sha256: f37d9c3fdfe8c9aae55fdfd5af815d24ce63c3a0f6a2c1f0982c30f43643fa1a + url: "https://pub.dev" + source: hosted + version: "5.2.2" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: "93fba3ad139dab2b1ce59ecc6fdce6da46a42cdb6c4399ecda30f1e7e725760d" + url: "https://pub.dev" + source: hosted + version: "6.12.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + 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: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + url: "https://pub.dev" + source: hosted + version: "0.12.18" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "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" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" + url: "https://pub.dev" + source: hosted + version: "0.17.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + 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: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + rfc_6901: + dependency: transitive + description: + name: rfc_6901 + sha256: "6a43b1858dca2febaf93e15639aa6b0c49ccdfd7647775f15a499f872b018154" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "1d562a3c1f713904ebbed50d2760217fd8a51ca170ac4b05b0db490699dbac17" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "4a85e90b50694e652075cbe4575665539d253e6ec10e46e76b45368ab5e3caae" + url: "https://pub.dev" + source: hosted + version: "1.3.10" + 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: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: transitive + description: + name: test + sha256: "54c516bbb7cee2754d327ad4fca637f78abfc3cbcc5ace83b3eda117e42cd71a" + url: "https://pub.dev" + source: hosted + version: "1.29.0" + test_api: + dependency: transitive + description: + name: test_api + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + url: "https://pub.dev" + source: hosted + version: "0.7.9" + test_core: + dependency: transitive + description: + name: test_core + sha256: "394f07d21f0f2255ec9e3989f21e54d3c7dc0e6e9dbce160e5a9c1a6be0e2943" + url: "https://pub.dev" + source: hosted + version: "0.6.15" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uri: + dependency: transitive + description: + name: uri + sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..d001ad3 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,100 @@ +name: relationship_saver +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.11.0 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + dio: ^5.9.1 + freezed_annotation: ^3.1.0 + json_annotation: ^4.10.0 + flutter_secure_storage: ^10.0.0 + uuid: ^4.5.2 + clock: ^1.1.2 + flutter_riverpod: ^3.2.1 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + freezed: ^3.2.5 + json_serializable: ^6.12.0 + build_runner: ^2.11.1 + mocktail: ^1.0.4 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/core/network/dio_error_mapper_test.dart b/test/core/network/dio_error_mapper_test.dart new file mode 100644 index 0000000..84028cd --- /dev/null +++ b/test/core/network/dio_error_mapper_test.dart @@ -0,0 +1,78 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:relationship_saver/core/network/backend_exception.dart'; +import 'package:relationship_saver/core/network/dio_error_mapper.dart'; + +void main() { + group('mapDioException', () { + test('maps key status codes', () { + expect(_mapStatus(401), isA()); + expect(_mapStatus(403), isA()); + expect(_mapStatus(404), isA()); + expect(_mapStatus(422), isA()); + expect(_mapStatus(429), isA()); + expect(_mapStatus(500), isA()); + }); + + test('maps timeout and network failures', () { + final RequestOptions options = RequestOptions(path: '/v1/me'); + + final TimeoutException timeout = + mapDioException( + DioException( + requestOptions: options, + type: DioExceptionType.connectionTimeout, + message: 'timed out', + ), + ) + as TimeoutException; + expect(timeout.message, contains('timed out')); + + expect( + mapDioException( + DioException( + requestOptions: options, + type: DioExceptionType.connectionError, + message: 'no route', + ), + ), + isA(), + ); + }); + + test('returns embedded backend exception unchanged', () { + final RequestOptions options = RequestOptions(path: '/v1/me'); + final AuthExpiredException embedded = const AuthExpiredException( + 'session expired', + ); + + final BackendException mapped = mapDioException( + DioException( + requestOptions: options, + type: DioExceptionType.badResponse, + error: embedded, + ), + ); + + expect(mapped, same(embedded)); + }); + }); +} + +BackendException _mapStatus(int status) { + final RequestOptions options = RequestOptions(path: '/v1/example'); + return mapDioException( + DioException( + requestOptions: options, + type: DioExceptionType.badResponse, + response: Response( + requestOptions: options, + statusCode: status, + data: {'code': 'ERR', 'message': 'failed'}, + headers: Headers.fromMap(>{ + 'x-request-id': ['req-123'], + }), + ), + ), + ); +} diff --git a/test/core/network/retry_interceptor_test.dart b/test/core/network/retry_interceptor_test.dart new file mode 100644 index 0000000..074d51f --- /dev/null +++ b/test/core/network/retry_interceptor_test.dart @@ -0,0 +1,71 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:relationship_saver/core/network/interceptors/retry_interceptor.dart'; +import 'package:relationship_saver/core/network/network_constants.dart'; + +import '../../helpers/queue_http_client_adapter.dart'; + +void main() { + test( + 'does not retry non-idempotent request without idempotency key', + () async { + final QueueHttpClientAdapter adapter = QueueHttpClientAdapter( + [ + (RequestOptions options, requestStream, cancelFuture) async { + return jsonResponse(500, { + 'code': 'SERVER_ERROR', + 'message': 'boom', + }); + }, + ], + ); + + final Dio dio = Dio(BaseOptions(baseUrl: 'https://api.example.com')); + dio.httpClientAdapter = adapter; + dio.interceptors.add( + RetryInterceptor(dio: dio, maxRetries: 2, baseDelay: Duration.zero), + ); + + await expectLater( + dio.post('/v1/auth/sign-out'), + throwsA(isA()), + ); + + expect(adapter.fetchCount, 1); + }, + ); + + test('retries non-idempotent request when idempotency key is set', () async { + final QueueHttpClientAdapter adapter = QueueHttpClientAdapter( + [ + (RequestOptions options, requestStream, cancelFuture) async { + return jsonResponse(500, { + 'code': 'SERVER_ERROR', + 'message': 'boom', + }); + }, + (RequestOptions options, requestStream, cancelFuture) async { + return jsonResponse(200, {'ok': true}); + }, + ], + ); + + final Dio dio = Dio(BaseOptions(baseUrl: 'https://api.example.com')); + dio.httpClientAdapter = adapter; + dio.interceptors.add( + RetryInterceptor(dio: dio, maxRetries: 2, baseDelay: Duration.zero), + ); + + final Response response = await dio.post( + '/v1/sync/push', + options: Options( + headers: { + NetworkConstants.idempotencyKeyHeader: 'idem-key-1', + }, + ), + ); + + expect(response.statusCode, 200); + expect(adapter.fetchCount, 2); + }); +} diff --git a/test/core/network/token_refresh_interceptor_test.dart b/test/core/network/token_refresh_interceptor_test.dart new file mode 100644 index 0000000..687c675 --- /dev/null +++ b/test/core/network/token_refresh_interceptor_test.dart @@ -0,0 +1,79 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:relationship_saver/core/auth/in_memory_token_store.dart'; +import 'package:relationship_saver/core/network/dio_factory.dart'; +import 'package:relationship_saver/integrations/backend/backend_gateway_rest.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +import '../../helpers/queue_http_client_adapter.dart'; + +void main() { + test('refreshes token once on 401 and retries original request', () async { + final InMemoryTokenStore tokenStore = InMemoryTokenStore(); + await tokenStore.write( + AuthSession( + accessToken: 'old-access', + refreshToken: 'old-refresh', + expiresAt: DateTime.utc(2026, 1, 1), + user: const UserProfile(id: 'u1', email: 'u1@example.com'), + ), + ); + + final QueueHttpClientAdapter adapter = QueueHttpClientAdapter( + [ + (RequestOptions options, requestStream, cancelFuture) async { + expect(options.path, '/v1/me'); + expect(options.headers['Authorization'], 'Bearer old-access'); + return jsonResponse( + 401, + {'code': 'AUTH_EXPIRED', 'message': 'expired'}, + headers: >{ + 'x-request-id': ['req-1'], + }, + ); + }, + (RequestOptions options, requestStream, cancelFuture) async { + expect(options.path, '/v1/auth/refresh'); + final Map body = Map.from( + options.data as Map, + ); + expect(body['refreshToken'], 'old-refresh'); + return jsonResponse(200, { + 'accessToken': 'new-access', + 'refreshToken': 'new-refresh', + 'expiresAt': '2026-01-02T00:00:00.000Z', + }); + }, + (RequestOptions options, requestStream, cancelFuture) async { + expect(options.path, '/v1/me'); + expect(options.headers['Authorization'], 'Bearer new-access'); + return jsonResponse(200, { + 'id': 'u1', + 'email': 'u1@example.com', + 'displayName': 'User One', + }); + }, + ], + ); + + final Dio dio = DioFactory.create( + baseUrl: 'https://api.example.com', + tokenStore: tokenStore, + httpClientAdapter: adapter, + ); + + final BackendGatewayRest gateway = BackendGatewayRest( + dio: dio, + tokenStore: tokenStore, + ); + + final UserProfile me = await gateway.me(); + + expect(me.id, 'u1'); + expect(adapter.fetchCount, 3); + + final AuthSession? stored = await tokenStore.read(); + expect(stored?.accessToken, 'new-access'); + expect(stored?.refreshToken, 'new-refresh'); + }); +} diff --git a/test/helpers/queue_http_client_adapter.dart b/test/helpers/queue_http_client_adapter.dart new file mode 100644 index 0000000..cace680 --- /dev/null +++ b/test/helpers/queue_http_client_adapter.dart @@ -0,0 +1,55 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; + +/// Sequential fake adapter for deterministic Dio unit tests. +typedef AdapterHandler = + Future Function( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ); + +class QueueHttpClientAdapter implements HttpClientAdapter { + QueueHttpClientAdapter(List handlers) + : _handlers = List.from(handlers); + + final List _handlers; + int fetchCount = 0; + + @override + void close({bool force = false}) {} + + @override + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, + ) async { + if (fetchCount >= _handlers.length) { + throw StateError( + 'No queued response for ${options.method} ${options.path} at call $fetchCount', + ); + } + + final AdapterHandler handler = _handlers[fetchCount]; + fetchCount += 1; + return handler(options, requestStream, cancelFuture); + } +} + +ResponseBody jsonResponse( + int statusCode, + Map body, { + Map> headers = const >{}, +}) { + return ResponseBody.fromString( + jsonEncode(body), + statusCode, + headers: >{ + Headers.contentTypeHeader: [Headers.jsonContentType], + ...headers, + }, + ); +} diff --git a/test/integrations/backend/backend_models_serialization_test.dart b/test/integrations/backend/backend_models_serialization_test.dart new file mode 100644 index 0000000..62985d2 --- /dev/null +++ b/test/integrations/backend/backend_models_serialization_test.dart @@ -0,0 +1,39 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; + +void main() { + test('ChangeEnvelope roundtrip serialization', () { + final ChangeEnvelope source = ChangeEnvelope( + schemaVersion: 1, + entityType: 'person', + entityId: 'p1', + op: ChangeOperation.upsert, + modifiedAt: DateTime.utc(2026, 2, 1), + clientMutationId: 'cm-1', + payload: const {'name': 'Alex'}, + ); + + final ChangeEnvelope decoded = ChangeEnvelope.fromJson(source.toJson()); + + expect(decoded, source); + }); + + test('SignalsFeed roundtrip serialization', () { + final SignalsFeed source = SignalsFeed( + cursor: 'c1', + items: [ + SignalItem( + id: 's1', + type: 'deal', + title: 'Coffee discount', + createdAt: DateTime.utc(2026, 2, 2, 10), + metadata: const {'merchant': 'Cafe'}, + ), + ], + ); + + final SignalsFeed decoded = SignalsFeed.fromJson(source.toJson()); + + expect(decoded, source); + }); +} diff --git a/test/integrations/backend/sync_envelope_validator_test.dart b/test/integrations/backend/sync_envelope_validator_test.dart new file mode 100644 index 0000000..d7e0b13 --- /dev/null +++ b/test/integrations/backend/sync_envelope_validator_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:relationship_saver/core/network/backend_exception.dart'; +import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; +import 'package:relationship_saver/integrations/backend/sync_envelope_validator.dart'; + +void main() { + test('rejects delete envelopes with payload', () { + final ChangeEnvelope invalid = ChangeEnvelope( + schemaVersion: 1, + entityType: 'person', + entityId: 'p1', + op: ChangeOperation.delete, + modifiedAt: DateTime.utc(2026, 1, 1), + clientMutationId: 'cm-delete', + payload: const {'unexpected': true}, + ); + + expect( + () => SyncEnvelopeValidator.validate(invalid), + throwsA(isA()), + ); + }); + + test('accepts valid upsert envelope', () { + final ChangeEnvelope valid = ChangeEnvelope( + schemaVersion: 1, + entityType: 'person', + entityId: 'p2', + op: ChangeOperation.upsert, + modifiedAt: DateTime.utc(2026, 1, 1), + clientMutationId: 'cm-upsert', + payload: const {'name': 'Jamie'}, + ); + + expect(() => SyncEnvelopeValidator.validate(valid), returnsNormally); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..dbb052b --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,14 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:relationship_saver/main.dart'; + +void main() { + testWidgets('renders app shell', (WidgetTester tester) async { + await tester.pumpWidget(const RelationshipSaverApp()); + + expect(find.text('Relationship Saver'), findsOneWidget); + expect( + find.textContaining('Backend gateway scaffold is ready.'), + findsOneWidget, + ); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..d77625a --- /dev/null +++ b/web/index.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + relationship_saver + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..3a51101 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "relationship_saver", + "short_name": "relationship_saver", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..37e0ff0 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(relationship_saver LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "relationship_saver") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..0c50753 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..4fc759c --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_secure_storage_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..5fa0bcb --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "relationship_saver" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "relationship_saver" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "relationship_saver.exe" "\0" + VALUE "ProductName", "relationship_saver" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..fffb331 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"relationship_saver", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_