diff --git a/.githooks/pre-commit b/.githooks/pre-commit
new file mode 100644
index 0000000..7dde48b
--- /dev/null
+++ b/.githooks/pre-commit
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+REPO_ROOT="$(git rev-parse --show-toplevel)"
+cd "$REPO_ROOT"
+
+mapfile -t STAGED_DART_FILES < <(
+ git diff --cached --name-only --diff-filter=ACMR \
+ | grep -E '^apps/mobile/.*\.dart$' \
+ | grep -Ev '\.(g|freezed)\.dart$' \
+ || true
+)
+
+mapfile -t STAGED_BRIDGE_FILES < <(
+ git diff --cached --name-only --diff-filter=ACMR \
+ | grep -E '^packages/bridge/' \
+ | grep -Ev '^packages/bridge/(dist|node_modules)/' \
+ | grep -E '\.(ts|js|cjs|mjs|json)$' \
+ || true
+)
+
+if [[ ${#STAGED_DART_FILES[@]} -eq 0 && ${#STAGED_BRIDGE_FILES[@]} -eq 0 ]]; then
+ echo "pre-commit: no staged mobile or bridge source files found; skipping checks."
+ exit 0
+fi
+
+if [[ ${#STAGED_DART_FILES[@]} -gt 0 ]]; then
+ echo "pre-commit: formatting staged Dart files"
+ dart format "${STAGED_DART_FILES[@]}"
+ git add "${STAGED_DART_FILES[@]}"
+
+ ANALYZE_TARGETS=()
+ for file in "${STAGED_DART_FILES[@]}"; do
+ ANALYZE_TARGETS+=("${file#apps/mobile/}")
+ done
+
+ echo "pre-commit: analyzing staged Dart files"
+ pushd apps/mobile > /dev/null
+ flutter analyze "${ANALYZE_TARGETS[@]}"
+ popd > /dev/null
+fi
+
+if [[ ${#STAGED_BRIDGE_FILES[@]} -gt 0 ]]; then
+ if [[ ! -d packages/bridge/node_modules ]]; then
+ echo "pre-commit: packages/bridge/node_modules is missing. Run 'cd packages/bridge && npm ci' first." >&2
+ exit 1
+ fi
+
+ BRIDGE_RELATIVE_FILES=()
+ for file in "${STAGED_BRIDGE_FILES[@]}"; do
+ BRIDGE_RELATIVE_FILES+=("${file#packages/bridge/}")
+ done
+
+ echo "pre-commit: formatting staged bridge files"
+ pushd packages/bridge > /dev/null
+ npm exec prettier -- --write "${BRIDGE_RELATIVE_FILES[@]}"
+ popd > /dev/null
+ git add "${STAGED_BRIDGE_FILES[@]}"
+
+ echo "pre-commit: typechecking bridge"
+ pushd packages/bridge > /dev/null
+ npm run typecheck
+
+ echo "pre-commit: running bridge tests"
+ npm run test:ci
+
+ echo "pre-commit: building bridge"
+ npm run build
+ popd > /dev/null
+fi
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..93de0db
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,164 @@
+name: Deploy
+
+on:
+ push:
+ tags:
+ - "v*"
+ workflow_dispatch:
+ inputs:
+ environment:
+ description: "Deployment environment"
+ required: true
+ default: staging
+ type: choice
+ options:
+ - staging
+ - production
+
+jobs:
+ build-android:
+ name: Build Android
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: apps/mobile
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: temurin
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.22.x"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Run code generation
+ run: dart run build_runner build --delete-conflicting-outputs
+
+ - name: Decode keystore
+ run: |
+ echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks
+
+ - name: Build App Bundle
+ env:
+ KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
+ KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
+ KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
+ run: flutter build appbundle --release
+
+ - name: Upload AAB artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: android-release
+ path: apps/mobile/build/app/outputs/bundle/release/app-release.aab
+
+ build-ios:
+ name: Build iOS
+ runs-on: macos-latest
+ defaults:
+ run:
+ working-directory: apps/mobile
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.22.x"
+ channel: stable
+ cache: true
+
+ - name: Install Ruby + Fastlane
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "3.3"
+ bundler-cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Run code generation
+ run: dart run build_runner build --delete-conflicting-outputs
+
+ - name: Fastlane — certificates
+ working-directory: .
+ env:
+ MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APP_PASSWORD }}
+ run: bundle exec fastlane ios certificates
+
+ - name: Fastlane — build
+ working-directory: .
+ env:
+ MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
+ MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
+ run: bundle exec fastlane ios build
+
+ - name: Upload IPA artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: ios-release
+ path: apps/mobile/build/ios/iphoneos/Runner.app
+
+ deploy-android:
+ name: Deploy Android
+ runs-on: ubuntu-latest
+ needs: build-android
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Download AAB
+ uses: actions/download-artifact@v4
+ with:
+ name: android-release
+
+ - name: Install Ruby + Fastlane
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "3.3"
+ bundler-cache: true
+
+ - name: Deploy to Play Store
+ env:
+ SUPPLY_JSON_KEY: ${{ secrets.GOOGLE_PLAY_JSON_KEY }}
+ run: bundle exec fastlane android deploy
+
+ deploy-ios:
+ name: Deploy iOS
+ runs-on: macos-latest
+ needs: build-ios
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Download IPA
+ uses: actions/download-artifact@v4
+ with:
+ name: ios-release
+
+ - name: Install Ruby + Fastlane
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "3.3"
+ bundler-cache: true
+
+ - name: Deploy to TestFlight
+ env:
+ FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APP_PASSWORD }}
+ FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }}
+ run: bundle exec fastlane ios testflight
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..5c10c0f
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,56 @@
+name: Deploy Docs to GitHub Pages
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "docs-site/**"
+ - ".github/workflows/docs.yml"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ deploy:
+ name: Deploy
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Configure GitHub Pages
+ uses: actions/configure-pages@v5
+
+ - name: Upload docs-site as Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: docs-site
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
+
+ update-repo:
+ name: Update repo metadata
+ runs-on: ubuntu-latest
+ needs: deploy
+ permissions:
+ contents: read
+ steps:
+ - name: Set homepage URL and description
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh repo edit RecursiveDev/ReCursor \
+ --homepage "https://recursivedev.github.io/ReCursor" \
+ --description "Mobile-first companion UI for AI coding workflows - Flutter app with OpenCode-like UX, Claude Code Hooks integration, and Agent SDK support"
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..79c6387
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,131 @@
+name: Test
+
+on:
+ pull_request:
+ branches: [main, develop]
+ push:
+ branches: [main, develop]
+
+jobs:
+ flutter-test:
+ name: Flutter Tests
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: apps/mobile
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.22.x"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Run code generation
+ run: dart run build_runner build --delete-conflicting-outputs
+
+ - name: Analyze
+ run: flutter analyze
+
+ - name: Run unit & widget tests
+ run: flutter test --coverage
+
+ - name: Upload coverage
+ uses: codecov/codecov-action@v4
+ with:
+ files: apps/mobile/coverage/lcov.info
+ flags: flutter
+
+ bridge-test:
+ name: Bridge Server Tests
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: packages/bridge
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "22"
+ cache: npm
+ cache-dependency-path: packages/bridge/package.json
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Type check
+ run: npm run typecheck
+
+ - name: Run tests
+ run: npm test
+
+ flutter-build-android:
+ name: Android Build Check
+ runs-on: ubuntu-latest
+ needs: flutter-test
+ if: github.event_name == 'push'
+ defaults:
+ run:
+ working-directory: apps/mobile
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: temurin
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.22.x"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Run code generation
+ run: dart run build_runner build --delete-conflicting-outputs
+
+ - name: Build APK (debug)
+ run: flutter build apk --debug
+
+ flutter-build-ios:
+ name: iOS Build Check
+ runs-on: macos-latest
+ needs: flutter-test
+ if: github.event_name == 'push'
+ defaults:
+ run:
+ working-directory: apps/mobile
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.22.x"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Run code generation
+ run: dart run build_runner build --delete-conflicting-outputs
+
+ - name: Build iOS (no codesign)
+ run: flutter build ios --debug --no-codesign
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..dc70d2e
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,6 @@
+source "https://rubygems.org"
+
+gem "fastlane", "~> 2.220"
+
+plugins_path = File.join(File.dirname(__FILE__), "fastlane", "Pluginfile")
+eval_gemfile(plugins_path) if File.exist?(plugins_path)
diff --git a/README.md b/README.md
index 3602074..6db52b3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
-
+
+
@@ -58,9 +58,10 @@
This repository is **work in progress**.
- ✅ Repo structure + documentation are being established.
-- ⏳ Flutter app and bridge server implementation are not yet shipped.
+- ✅ The mobile direction is now bridge-first: pair with your local bridge, no sign-in required.
+- ⏳ Flutter app and bridge server implementation are still being completed.
-If you're new here, start with: **`docs/README.md`**.
+If you're new here, start with: **`docs/README.md`** and the bridge pairing flow in `docs/wireframes/01-startup.md`.
---
@@ -75,11 +76,18 @@ If you're new here, start with: **`docs/README.md`**.
### Important constraint (Claude Code)
-Claude Code's **Remote Control** feature is **first-party** (designed for `claude.ai/code` and official Claude apps). ReCursor docs are written to avoid claiming access to any private/undocumented Remote Control protocol.
+Claude Code's **Remote Control** feature is **first-party only** (designed for `claude.ai/code` and official Claude apps). There is no public API for third-party clients to join or mirror existing Claude Code sessions.
-ReCursor's supported approach is:
-- **Claude Code Hooks**: event observation (one-way)
-- **Claude Agent SDK**: a **parallel, controllable** agent session that ReCursor can drive (approvals/tool execution live here)
+ReCursor's supported integration paths:
+- **Claude Code Hooks**: HTTP-based event observation (one-way)
+- **Claude Agent SDK**: **parallel, controllable** agent sessions that ReCursor can drive
+
+### Bridge-first, no-login workflow
+
+ReCursor uses a **bridge-first** connection model:
+- The mobile app connects directly to a **user-controlled desktop bridge** (no hosted service, no user accounts)
+- On startup, the app restores saved bridge pairings or guides through QR-code pairing
+- Remote access is achieved via secure tunnels (Tailscale, WireGuard) to the user's own bridge — not through unsupported third-party Claude Remote Control access
---
diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore
new file mode 100644
index 0000000..3820a95
--- /dev/null
+++ b/apps/mobile/.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/apps/mobile/.metadata b/apps/mobile/.metadata
new file mode 100644
index 0000000..df13aa7
--- /dev/null
+++ b/apps/mobile/.metadata
@@ -0,0 +1,30 @@
+# 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: "ff37bef603469fb030f2b72995ab929ccfc227f0"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
+ base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
+ - platform: android
+ create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
+ base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
+
+ # 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/apps/mobile/README.md b/apps/mobile/README.md
new file mode 100644
index 0000000..6ac17cf
--- /dev/null
+++ b/apps/mobile/README.md
@@ -0,0 +1,17 @@
+# recursor_mobile
+
+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/apps/mobile/analysis_options.yaml b/apps/mobile/analysis_options.yaml
index 388cd92..7e160a5 100644
--- a/apps/mobile/analysis_options.yaml
+++ b/apps/mobile/analysis_options.yaml
@@ -1,3 +1,18 @@
include: package:flutter_lints/flutter.yaml
-# Scaffold-only: customize analysis/lints as the app is implemented.
+analyzer:
+ exclude:
+ - '**/*.g.dart'
+ - '**/*.freezed.dart'
+
+linter:
+ rules:
+ always_declare_return_types: true
+ avoid_print: true
+ directives_ordering: true
+ prefer_const_constructors: true
+ prefer_const_declarations: true
+ prefer_final_fields: true
+ prefer_final_locals: true
+ sort_child_properties_last: true
+ unawaited_futures: true
diff --git a/apps/mobile/android/.gitignore b/apps/mobile/android/.gitignore
new file mode 100644
index 0000000..be3943c
--- /dev/null
+++ b/apps/mobile/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/apps/mobile/android/app/build.gradle.kts b/apps/mobile/android/app/build.gradle.kts
new file mode 100644
index 0000000..3eee2d7
--- /dev/null
+++ b/apps/mobile/android/app/build.gradle.kts
@@ -0,0 +1,79 @@
+import java.util.Properties
+
+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")
+}
+
+val localProperties = Properties().apply {
+ val localPropertiesFile = rootProject.file("local.properties")
+ if (localPropertiesFile.exists()) {
+ localPropertiesFile.inputStream().use(::load)
+ }
+}
+
+val isWindows = System.getProperty("os.name").contains("Windows", ignoreCase = true)
+val flutterSdk = localProperties.getProperty("flutter.sdk") ?: System.getenv("FLUTTER_ROOT")
+val flutterExecutable = when {
+ !flutterSdk.isNullOrBlank() && isWindows -> "$flutterSdk\\bin\\flutter.bat"
+ !flutterSdk.isNullOrBlank() -> "$flutterSdk/bin/flutter"
+ isWindows -> "flutter.bat"
+ else -> "flutter"
+}
+val flutterProjectDir = rootProject.projectDir.parentFile
+
+val analyzeFlutterBeforeBuild = tasks.register("analyzeFlutterBeforeBuild") {
+ group = "verification"
+ description = "Runs flutter analyze before Android builds."
+ workingDir = flutterProjectDir
+ commandLine(flutterExecutable, "analyze", "--no-fatal-infos", "--no-fatal-warnings")
+}
+
+tasks.named("preBuild") {
+ dependsOn(analyzeFlutterBeforeBuild)
+}
+
+android {
+ namespace = "com.example.recursor_mobile"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ isCoreLibraryDesugaringEnabled = true
+ }
+
+ 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.recursor_mobile"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ minSdk = flutter.minSdkVersion
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+flutter {
+ source = "../.."
+}
+
+dependencies {
+ coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
+}
diff --git a/apps/mobile/android/app/src/debug/AndroidManifest.xml b/apps/mobile/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/apps/mobile/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/apps/mobile/android/app/src/main/AndroidManifest.xml b/apps/mobile/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2612599
--- /dev/null
+++ b/apps/mobile/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/mobile/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/apps/mobile/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
index 539ab02..fc67028 100644
--- a/apps/mobile/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
+++ b/apps/mobile/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
@@ -15,5 +15,60 @@
public final class GeneratedPluginRegistrant {
private static final String TAG = "GeneratedPluginRegistrant";
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
+ try {
+ flutterEngine.getPlugins().add(new com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin flutter_local_notifications, com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin flutter_secure_storage, com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new dev.flutter.plugins.integration_test.IntegrationTestPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin integration_test, dev.flutter.plugins.integration_test.IntegrationTestPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new com.github.dart_lang.jni.JniPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin jni, com.github.dart_lang.jni.JniPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new dev.steenbakker.mobile_scanner.MobileScannerPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin mobile_scanner, dev.steenbakker.mobile_scanner.MobileScannerPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin package_info_plus, dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new pl.leancode.patrol.PatrolPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin patrol, pl.leancode.patrol.PatrolPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new io.sentry.flutter.SentryFlutterPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin sentry_flutter, io.sentry.flutter.SentryFlutterPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new com.csdcorp.speech_to_text.SpeechToTextPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin speech_to_text, com.csdcorp.speech_to_text.SpeechToTextPlugin", e);
+ }
+ try {
+ flutterEngine.getPlugins().add(new eu.simonbinder.sqlite3_flutter_libs.Sqlite3FlutterLibsPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin sqlite3_flutter_libs, eu.simonbinder.sqlite3_flutter_libs.Sqlite3FlutterLibsPlugin", e);
+ }
}
}
diff --git a/apps/mobile/android/app/src/main/kotlin/com/example/recursor_mobile/MainActivity.kt b/apps/mobile/android/app/src/main/kotlin/com/example/recursor_mobile/MainActivity.kt
new file mode 100644
index 0000000..b9aa7f6
--- /dev/null
+++ b/apps/mobile/android/app/src/main/kotlin/com/example/recursor_mobile/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.recursor_mobile
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/apps/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/apps/mobile/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/apps/mobile/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/apps/mobile/android/app/src/main/res/drawable/launch_background.xml b/apps/mobile/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/apps/mobile/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/apps/mobile/android/app/src/main/res/values-night/styles.xml b/apps/mobile/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/apps/mobile/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/apps/mobile/android/app/src/main/res/values/styles.xml b/apps/mobile/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/apps/mobile/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/apps/mobile/android/app/src/profile/AndroidManifest.xml b/apps/mobile/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/apps/mobile/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/apps/mobile/android/build.gradle.kts b/apps/mobile/android/build.gradle.kts
new file mode 100644
index 0000000..dbee657
--- /dev/null
+++ b/apps/mobile/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/apps/mobile/android/gradle.properties b/apps/mobile/android/gradle.properties
new file mode 100644
index 0000000..fbee1d8
--- /dev/null
+++ b/apps/mobile/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/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e4ef43f
--- /dev/null
+++ b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
diff --git a/apps/mobile/android/settings.gradle.kts b/apps/mobile/android/settings.gradle.kts
new file mode 100644
index 0000000..ca7fe06
--- /dev/null
+++ b/apps/mobile/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/apps/mobile/assets/branding/ReCursor_Darklogo.png b/apps/mobile/assets/branding/ReCursor_Darklogo.png
deleted file mode 100644
index d0595dc..0000000
Binary files a/apps/mobile/assets/branding/ReCursor_Darklogo.png and /dev/null differ
diff --git a/apps/mobile/assets/branding/ReCursor_Lightlogo.png b/apps/mobile/assets/branding/ReCursor_Lightlogo.png
deleted file mode 100644
index 798e148..0000000
Binary files a/apps/mobile/assets/branding/ReCursor_Lightlogo.png and /dev/null differ
diff --git a/apps/mobile/assets/branding/recursor_logo_dark.svg b/apps/mobile/assets/branding/recursor_logo_dark.svg
new file mode 100644
index 0000000..398cc6a
--- /dev/null
+++ b/apps/mobile/assets/branding/recursor_logo_dark.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/apps/mobile/assets/branding/recursor_logo_light.svg b/apps/mobile/assets/branding/recursor_logo_light.svg
new file mode 100644
index 0000000..e32b9f3
--- /dev/null
+++ b/apps/mobile/assets/branding/recursor_logo_light.svg
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/apps/mobile/assets/fonts/JetBrainsMono-Bold.ttf b/apps/mobile/assets/fonts/JetBrainsMono-Bold.ttf
new file mode 100644
index 0000000..cd1bee0
Binary files /dev/null and b/apps/mobile/assets/fonts/JetBrainsMono-Bold.ttf differ
diff --git a/apps/mobile/assets/fonts/JetBrainsMono-Regular.ttf b/apps/mobile/assets/fonts/JetBrainsMono-Regular.ttf
new file mode 100644
index 0000000..711830e
Binary files /dev/null and b/apps/mobile/assets/fonts/JetBrainsMono-Regular.ttf differ
diff --git a/apps/mobile/integration_test/app_test.dart b/apps/mobile/integration_test/app_test.dart
new file mode 100644
index 0000000..43f480b
--- /dev/null
+++ b/apps/mobile/integration_test/app_test.dart
@@ -0,0 +1,90 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:recursor_mobile/app.dart';
+import 'package:recursor_mobile/core/network/websocket_service.dart';
+import 'package:recursor_mobile/core/providers/theme_provider.dart';
+import 'package:recursor_mobile/core/providers/token_storage_provider.dart';
+import 'package:recursor_mobile/core/storage/preferences.dart';
+import 'package:recursor_mobile/core/storage/secure_token_storage.dart';
+import 'package:recursor_mobile/features/startup/domain/bridge_startup_controller.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ testWidgets('launch routes to bridge setup without any login workflow', (
+ tester,
+ ) async {
+ final preferences = FakeAppPreferences();
+ final tokenStorage = FakeSecureTokenStorage();
+ final startupController = FakeBridgeStartupController(
+ const AppStartupResult.bridgeSetup(),
+ );
+
+ await tester.pumpWidget(
+ ProviderScope(
+ overrides: [
+ appPreferencesProvider.overrideWithValue(preferences),
+ tokenStorageProvider.overrideWithValue(tokenStorage),
+ bridgeStartupControllerProvider.overrideWithValue(startupController),
+ ],
+ child: const ReCursorApp(),
+ ),
+ );
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 1300));
+ await tester.pumpAndSettle();
+
+ expect(find.byKey(const Key('bridgeSetupScreen')), findsOneWidget);
+ expect(find.text('Bridge Setup'), findsOneWidget);
+ expect(find.textContaining('Sign in'), findsNothing);
+ expect(find.textContaining('GitHub'), findsNothing);
+ });
+}
+
+class FakeBridgeStartupController extends BridgeStartupController {
+ FakeBridgeStartupController(this.result)
+ : super(
+ preferences: FakeAppPreferences(),
+ tokenStorage: FakeSecureTokenStorage(),
+ webSocketService: _NoopWebSocketService(),
+ );
+
+ final AppStartupResult result;
+
+ @override
+ Future restore() async {
+ return result;
+ }
+}
+
+class FakeAppPreferences extends AppPreferences {
+ FakeAppPreferences({this.bridgeUrl});
+
+ String? bridgeUrl;
+
+ @override
+ String? getBridgeUrl() {
+ return bridgeUrl;
+ }
+
+ @override
+ Future setBridgeUrl(String? url) async {
+ bridgeUrl = url;
+ }
+}
+
+class FakeSecureTokenStorage extends SecureTokenStorage {
+ FakeSecureTokenStorage({this.token}) : super(const FlutterSecureStorage());
+
+ String? token;
+
+ @override
+ Future getToken(String key) async {
+ return token;
+ }
+}
+
+class _NoopWebSocketService extends WebSocketService {}
diff --git a/apps/mobile/integration_test/chat_test.dart b/apps/mobile/integration_test/chat_test.dart
new file mode 100644
index 0000000..e6a7b11
--- /dev/null
+++ b/apps/mobile/integration_test/chat_test.dart
@@ -0,0 +1,116 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router/go_router.dart';
+import 'package:integration_test/integration_test.dart';
+import 'package:recursor_mobile/core/network/connection_state.dart';
+import 'package:recursor_mobile/core/network/websocket_messages.dart';
+import 'package:recursor_mobile/core/network/websocket_service.dart';
+import 'package:recursor_mobile/core/providers/database_provider.dart';
+import 'package:recursor_mobile/core/providers/websocket_provider.dart';
+import 'package:recursor_mobile/core/storage/database.dart';
+import 'package:recursor_mobile/features/chat/presentation/screens/session_list_screen.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ testWidgets('starting a chat session sends a session_start request',
+ (tester) async {
+ final database = AppDatabase.inMemory();
+ final webSocketService = FakeIntegrationWebSocketService(
+ initialStatus: ConnectionStatus.connected,
+ );
+ final router = GoRouter(
+ routes: [
+ GoRoute(
+ path: '/',
+ builder: (_, __) => const SessionListScreen(),
+ ),
+ GoRoute(
+ path: '/home/chat/:sessionId',
+ builder: (_, state) => Scaffold(
+ body: Center(
+ child: Text('Chat ${state.pathParameters['sessionId']}'),
+ ),
+ ),
+ ),
+ ],
+ );
+
+ addTearDown(() async {
+ router.dispose();
+ await webSocketService.close();
+ await database.close();
+ });
+
+ await tester.pumpWidget(
+ ProviderScope(
+ overrides: [
+ databaseProvider.overrideWithValue(database),
+ webSocketServiceProvider.overrideWithValue(webSocketService),
+ ],
+ child: MaterialApp.router(routerConfig: router),
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ await tester.tap(find.text('New Session'));
+ await tester.pumpAndSettle();
+ await tester.enterText(
+ find.byKey(const Key('newSessionWorkingDirectoryField')),
+ '/workspace/integration-project',
+ );
+ await tester.tap(find.text('Start Session'));
+ await tester.pumpAndSettle();
+
+ expect(webSocketService.sentMessages, hasLength(1));
+ expect(webSocketService.sentMessages.single.type,
+ BridgeMessageType.sessionStart);
+ expect(
+ webSocketService.sentMessages.single.payload['working_directory'],
+ '/workspace/integration-project',
+ );
+ expect(find.textContaining('Chat '), findsOneWidget);
+
+ final sessions = await database.select(database.sessions).get();
+ expect(sessions, hasLength(1));
+ expect(sessions.single.workingDirectory, '/workspace/integration-project');
+ });
+}
+
+class FakeIntegrationWebSocketService extends WebSocketService {
+ FakeIntegrationWebSocketService({required ConnectionStatus initialStatus})
+ : _currentStatus = initialStatus;
+
+ final StreamController _messageController =
+ StreamController.broadcast();
+ final StreamController _statusController =
+ StreamController.broadcast();
+ final List sentMessages = [];
+ final ConnectionStatus _currentStatus;
+
+ @override
+ Stream get messages => _messageController.stream;
+
+ @override
+ Stream get connectionStatus => _statusController.stream;
+
+ @override
+ ConnectionStatus get currentStatus => _currentStatus;
+
+ @override
+ bool send(BridgeMessage message) {
+ if (_currentStatus != ConnectionStatus.connected) {
+ return false;
+ }
+ sentMessages.add(message);
+ return true;
+ }
+
+ Future close() async {
+ await _messageController.close();
+ await _statusController.close();
+ }
+}
diff --git a/apps/mobile/integration_test/patrol_test_config.dart b/apps/mobile/integration_test/patrol_test_config.dart
new file mode 100644
index 0000000..6ed1edd
--- /dev/null
+++ b/apps/mobile/integration_test/patrol_test_config.dart
@@ -0,0 +1,2 @@
+// Patrol test configuration
+// Run with: flutter test integration_test/ --dart-define=PATROL=true
diff --git a/apps/mobile/ios/Runner/GeneratedPluginRegistrant.m b/apps/mobile/ios/Runner/GeneratedPluginRegistrant.m
index efe65ec..1d16b8f 100644
--- a/apps/mobile/ios/Runner/GeneratedPluginRegistrant.m
+++ b/apps/mobile/ios/Runner/GeneratedPluginRegistrant.m
@@ -6,9 +6,72 @@
#import "GeneratedPluginRegistrant.h"
+#if __has_include()
+#import
+#else
+@import flutter_local_notifications;
+#endif
+
+#if __has_include()
+#import
+#else
+@import flutter_secure_storage;
+#endif
+
+#if __has_include()
+#import
+#else
+@import integration_test;
+#endif
+
+#if __has_include()
+#import
+#else
+@import mobile_scanner;
+#endif
+
+#if __has_include()
+#import
+#else
+@import package_info_plus;
+#endif
+
+#if __has_include()
+#import
+#else
+@import patrol;
+#endif
+
+#if __has_include()
+#import
+#else
+@import sentry_flutter;
+#endif
+
+#if __has_include()
+#import
+#else
+@import speech_to_text;
+#endif
+
+#if __has_include()
+#import
+#else
+@import sqlite3_flutter_libs;
+#endif
+
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject*)registry {
+ [FlutterLocalNotificationsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterLocalNotificationsPlugin"]];
+ [FlutterSecureStoragePlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterSecureStoragePlugin"]];
+ [IntegrationTestPlugin registerWithRegistrar:[registry registrarForPlugin:@"IntegrationTestPlugin"]];
+ [MobileScannerPlugin registerWithRegistrar:[registry registrarForPlugin:@"MobileScannerPlugin"]];
+ [FPPPackageInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FPPPackageInfoPlusPlugin"]];
+ [PatrolPlugin registerWithRegistrar:[registry registrarForPlugin:@"PatrolPlugin"]];
+ [SentryFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"SentryFlutterPlugin"]];
+ [SpeechToTextPlugin registerWithRegistrar:[registry registrarForPlugin:@"SpeechToTextPlugin"]];
+ [Sqlite3FlutterLibsPlugin registerWithRegistrar:[registry registrarForPlugin:@"Sqlite3FlutterLibsPlugin"]];
}
@end
diff --git a/apps/mobile/lib/app.dart b/apps/mobile/lib/app.dart
new file mode 100644
index 0000000..91bc8a1
--- /dev/null
+++ b/apps/mobile/lib/app.dart
@@ -0,0 +1,29 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import 'core/config/router.dart';
+import 'core/config/theme.dart';
+import 'core/providers/theme_provider.dart';
+
+class ReCursorApp extends ConsumerWidget {
+ const ReCursorApp({super.key});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final router = ref.watch(routerProvider);
+ final themeMode = ref.watch(themeModeProvider);
+ final highContrast = ref.watch(highContrastProvider);
+
+ final effectiveDarkTheme =
+ highContrast ? AppTheme.highContrastTheme : AppTheme.darkTheme;
+
+ return MaterialApp.router(
+ title: 'ReCursor',
+ theme: highContrast ? AppTheme.highContrastTheme : AppTheme.lightTheme,
+ darkTheme: effectiveDarkTheme,
+ themeMode: themeMode,
+ routerConfig: router,
+ debugShowCheckedModeBanner: false,
+ );
+ }
+}
diff --git a/apps/mobile/lib/core/config/app_config.dart b/apps/mobile/lib/core/config/app_config.dart
new file mode 100644
index 0000000..1b1eb66
--- /dev/null
+++ b/apps/mobile/lib/core/config/app_config.dart
@@ -0,0 +1,19 @@
+/// Application-wide constants.
+class AppConfig {
+ const AppConfig._();
+
+ static const String appName = 'ReCursor';
+ static const String version = '0.1.0';
+
+ /// Heartbeat ping interval in seconds.
+ static const int heartbeatInterval = 15;
+
+ /// Seconds to wait for a heartbeat pong before triggering reconnect.
+ static const int heartbeatTimeout = 10;
+
+ /// Maximum number of reconnect attempts before giving up.
+ static const int maxReconnectAttempts = 10;
+
+ /// Maximum number of items stored in the offline sync queue.
+ static const int maxSyncQueueSize = 500;
+}
diff --git a/apps/mobile/lib/core/config/high_contrast_theme.dart b/apps/mobile/lib/core/config/high_contrast_theme.dart
new file mode 100644
index 0000000..6815fe4
--- /dev/null
+++ b/apps/mobile/lib/core/config/high_contrast_theme.dart
@@ -0,0 +1,56 @@
+import 'package:flutter/material.dart';
+
+class HighContrastTheme {
+ static ThemeData get theme {
+ return ThemeData.dark().copyWith(
+ colorScheme: const ColorScheme.dark(
+ primary: Color(0xFFFFFFFF), // white
+ secondary: Color(0xFFFFFF00), // yellow
+ surface: Color(0xFF000000), // pure black
+ error: Color(0xFFFF0000), // pure red
+ ),
+ scaffoldBackgroundColor: Colors.black,
+ cardTheme: const CardThemeData(
+ color: Color(0xFF111111),
+ elevation: 0,
+ shape: RoundedRectangleBorder(
+ side: BorderSide(color: Colors.white, width: 1),
+ ),
+ ),
+ appBarTheme: const AppBarTheme(
+ backgroundColor: Colors.black,
+ foregroundColor: Colors.white,
+ elevation: 0,
+ shape: Border(bottom: BorderSide(color: Colors.white, width: 1)),
+ ),
+ textTheme: const TextTheme(
+ bodyLarge: TextStyle(color: Colors.white, fontSize: 16),
+ bodyMedium: TextStyle(color: Colors.white, fontSize: 14),
+ bodySmall: TextStyle(color: Colors.white, fontSize: 12),
+ titleLarge: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
+ ),
+ iconTheme: const IconThemeData(color: Colors.white),
+ dividerColor: Colors.white,
+ inputDecorationTheme: InputDecorationTheme(
+ filled: true,
+ fillColor: Colors.black,
+ border: const OutlineInputBorder(
+ borderSide: BorderSide(color: Colors.white, width: 2),
+ ),
+ enabledBorder: const OutlineInputBorder(
+ borderSide: BorderSide(color: Colors.white, width: 2),
+ ),
+ focusedBorder: const OutlineInputBorder(
+ borderSide: BorderSide(color: Colors.yellow, width: 2),
+ ),
+ labelStyle: const TextStyle(color: Colors.white),
+ hintStyle: const TextStyle(color: Colors.grey),
+ ),
+ bottomNavigationBarTheme: const BottomNavigationBarThemeData(
+ backgroundColor: Colors.black,
+ selectedItemColor: Colors.yellow,
+ unselectedItemColor: Colors.white,
+ ),
+ );
+ }
+}
diff --git a/apps/mobile/lib/core/config/router.dart b/apps/mobile/lib/core/config/router.dart
new file mode 100644
index 0000000..e5c7704
--- /dev/null
+++ b/apps/mobile/lib/core/config/router.dart
@@ -0,0 +1,130 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+
+import '../../features/agents/presentation/screens/agent_list_screen.dart';
+import '../../features/approvals/presentation/screens/approval_detail_screen.dart';
+import '../../features/approvals/presentation/screens/approvals_screen.dart';
+import '../../features/chat/presentation/screens/chat_screen.dart';
+import '../../features/chat/presentation/screens/session_list_screen.dart';
+import '../../features/diff/presentation/screens/diff_viewer_screen.dart';
+import '../../features/git/presentation/screens/git_screen.dart';
+import '../../features/home/home_shell.dart';
+import '../../features/repos/presentation/screens/file_tree_screen.dart';
+import '../../features/repos/presentation/screens/file_viewer_screen.dart';
+import '../../features/settings/presentation/screens/settings_screen.dart';
+import '../../features/startup/presentation/screens/bridge_setup_screen.dart';
+import '../../features/startup/presentation/screens/splash_screen.dart';
+import '../../features/terminal/presentation/screens/terminal_screen.dart';
+
+GoRouter _buildRouter() {
+ return GoRouter(
+ initialLocation: '/splash',
+ routes: [
+ GoRoute(path: '/', redirect: (_, __) => '/splash'),
+ GoRoute(
+ path: '/splash',
+ builder: (_, __) => const SplashScreen(),
+ ),
+ GoRoute(
+ path: '/bridge-setup',
+ builder: (_, __) => const BridgeSetupScreen(),
+ ),
+ StatefulShellRoute.indexedStack(
+ builder: (context, state, navigationShell) =>
+ HomeShell(navigationShell: navigationShell),
+ branches: [
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/home/chat',
+ builder: (_, __) => const SessionListScreen(),
+ routes: [
+ GoRoute(
+ path: ':sessionId',
+ builder: (_, state) => ChatScreen(
+ sessionId: state.pathParameters['sessionId']!,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/home/diff',
+ builder: (_, __) => const DiffViewerScreen(),
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/home/repos',
+ builder: (_, state) {
+ final sessionId =
+ state.uri.queryParameters['sessionId'] ?? '';
+ return FileTreeScreen(sessionId: sessionId);
+ },
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/home/git',
+ builder: (_, __) => const GitScreen(sessionId: ''),
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/home/approvals',
+ builder: (_, __) => const ApprovalsScreen(),
+ ),
+ ],
+ ),
+ StatefulShellBranch(
+ routes: [
+ GoRoute(
+ path: '/home/settings',
+ builder: (_, __) => const SettingsScreen(),
+ ),
+ ],
+ ),
+ ],
+ ),
+ GoRoute(
+ path: '/home/agents',
+ builder: (_, __) => const AgentListScreen(),
+ ),
+ GoRoute(
+ path: '/approval/:id',
+ builder: (_, state) =>
+ ApprovalDetailScreen(toolCallId: state.pathParameters['id']!),
+ ),
+ GoRoute(
+ path: '/terminal',
+ builder: (_, __) =>
+ const TerminalScreen(sessionId: 'default', workingDirectory: '~'),
+ ),
+ GoRoute(
+ path: '/home/repos/view',
+ builder: (_, state) {
+ final extra = state.extra as Map? ?? {};
+ return FileViewerScreen(
+ sessionId: extra['sessionId'] ?? '',
+ path: extra['path'] ?? '',
+ );
+ },
+ ),
+ ],
+ );
+}
+
+final routerProvider = Provider((ref) {
+ return _buildRouter();
+});
+
+final appRouter = Provider((ref) => ref.watch(routerProvider));
diff --git a/apps/mobile/lib/core/config/theme.dart b/apps/mobile/lib/core/config/theme.dart
new file mode 100644
index 0000000..59b6a98
--- /dev/null
+++ b/apps/mobile/lib/core/config/theme.dart
@@ -0,0 +1,173 @@
+import 'package:flutter/material.dart';
+
+import 'high_contrast_theme.dart';
+
+class AppColors {
+ static const background = Color(0xFF121212);
+ static const surface = Color(0xFF1E1E1E);
+ static const surfaceVariant = Color(0xFF252526);
+ static const primary = Color(0xFF569CD6);
+ static const secondary = Color(0xFF4EC9B0);
+ static const error = Color(0xFFF44747);
+ static const added = Color(0xFF4EC9B0);
+ static const removed = Color(0xFFF44747);
+ static const accent = Color(0xFFCE9178);
+ static const textPrimary = Color(0xFFD4D4D4);
+ static const textSecondary = Color(0xFF9E9E9E);
+ static const border = Color(0xFF3E3E3E);
+}
+
+class AppTheme {
+ static ThemeData get darkTheme {
+ const colorScheme = ColorScheme.dark(
+ surface: AppColors.surface,
+ background: AppColors.background,
+ primary: AppColors.primary,
+ secondary: AppColors.secondary,
+ error: AppColors.error,
+ onSurface: AppColors.textPrimary,
+ onBackground: AppColors.textPrimary,
+ onPrimary: Colors.white,
+ onSecondary: Colors.white,
+ );
+
+ return ThemeData(
+ useMaterial3: true,
+ colorScheme: colorScheme,
+ scaffoldBackgroundColor: AppColors.background,
+ cardColor: AppColors.surfaceVariant,
+ cardTheme: const CardThemeData(
+ color: AppColors.surfaceVariant,
+ elevation: 2,
+ margin: EdgeInsets.zero,
+ ),
+ appBarTheme: const AppBarTheme(
+ backgroundColor: AppColors.surface,
+ elevation: 0,
+ foregroundColor: AppColors.textPrimary,
+ titleTextStyle: TextStyle(
+ color: AppColors.textPrimary,
+ fontSize: 16,
+ fontWeight: FontWeight.w600,
+ ),
+ iconTheme: IconThemeData(color: AppColors.textPrimary),
+ ),
+ bottomNavigationBarTheme: const BottomNavigationBarThemeData(
+ backgroundColor: AppColors.surface,
+ selectedItemColor: AppColors.primary,
+ unselectedItemColor: AppColors.textSecondary,
+ type: BottomNavigationBarType.fixed,
+ elevation: 0,
+ ),
+ inputDecorationTheme: InputDecorationTheme(
+ filled: true,
+ fillColor: AppColors.surfaceVariant,
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: AppColors.border),
+ ),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: AppColors.border),
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: AppColors.primary),
+ ),
+ errorBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: AppColors.error),
+ ),
+ hintStyle: const TextStyle(color: AppColors.textSecondary),
+ labelStyle: const TextStyle(color: AppColors.textSecondary),
+ ),
+ textTheme: const TextTheme(
+ bodyMedium: TextStyle(
+ fontFamily: 'JetBrainsMono',
+ fontSize: 14,
+ color: AppColors.textPrimary,
+ ),
+ bodyLarge: TextStyle(
+ fontSize: 16,
+ color: AppColors.textPrimary,
+ ),
+ bodySmall: TextStyle(
+ fontSize: 12,
+ color: AppColors.textSecondary,
+ ),
+ titleLarge: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w600,
+ color: AppColors.textPrimary,
+ ),
+ titleMedium: TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ color: AppColors.textPrimary,
+ ),
+ titleSmall: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ color: AppColors.textPrimary,
+ ),
+ labelLarge: TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w500,
+ color: AppColors.textPrimary,
+ ),
+ labelMedium: TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ color: AppColors.textSecondary,
+ ),
+ ),
+ iconTheme: const IconThemeData(color: AppColors.textPrimary),
+ dividerTheme: const DividerThemeData(
+ color: AppColors.border,
+ thickness: 1,
+ ),
+ switchTheme: SwitchThemeData(
+ thumbColor: MaterialStateProperty.resolveWith((states) {
+ if (states.contains(MaterialState.selected)) return AppColors.primary;
+ return AppColors.textSecondary;
+ }),
+ trackColor: MaterialStateProperty.resolveWith((states) {
+ if (states.contains(MaterialState.selected)) {
+ return AppColors.primary.withOpacity(0.4);
+ }
+ return AppColors.border;
+ }),
+ ),
+ );
+ }
+
+ static ThemeData get highContrastTheme => HighContrastTheme.theme;
+
+ static ThemeData get lightTheme {
+ const colorScheme = ColorScheme.light(
+ primary: AppColors.primary,
+ secondary: AppColors.secondary,
+ error: AppColors.error,
+ );
+
+ return ThemeData(
+ useMaterial3: true,
+ colorScheme: colorScheme,
+ cardTheme: const CardThemeData(elevation: 2),
+ appBarTheme: const AppBarTheme(elevation: 0),
+ inputDecorationTheme: InputDecorationTheme(
+ filled: true,
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8),
+ borderSide: const BorderSide(color: AppColors.primary),
+ ),
+ ),
+ );
+ }
+}
diff --git a/apps/mobile/lib/core/models/agent_models.dart b/apps/mobile/lib/core/models/agent_models.dart
new file mode 100644
index 0000000..7e0a823
--- /dev/null
+++ b/apps/mobile/lib/core/models/agent_models.dart
@@ -0,0 +1,27 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'agent_models.freezed.dart';
+part 'agent_models.g.dart';
+
+enum AgentType { claudeCode, openCode, aider, goose, custom }
+
+enum AgentConnectionStatus { connected, disconnected, inactive }
+
+@freezed
+class AgentConfig with _$AgentConfig {
+ const factory AgentConfig({
+ required String id,
+ required String displayName,
+ required AgentType type,
+ required String bridgeUrl,
+ required String authToken,
+ String? workingDirectory,
+ @Default(AgentConnectionStatus.disconnected) AgentConnectionStatus status,
+ DateTime? lastConnectedAt,
+ required DateTime createdAt,
+ required DateTime updatedAt,
+ }) = _AgentConfig;
+
+ factory AgentConfig.fromJson(Map json) =>
+ _$AgentConfigFromJson(json);
+}
diff --git a/apps/mobile/lib/core/models/agent_models.freezed.dart b/apps/mobile/lib/core/models/agent_models.freezed.dart
new file mode 100644
index 0000000..4b69590
--- /dev/null
+++ b/apps/mobile/lib/core/models/agent_models.freezed.dart
@@ -0,0 +1,367 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// 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 'agent_models.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+AgentConfig _$AgentConfigFromJson(Map json) {
+ return _AgentConfig.fromJson(json);
+}
+
+/// @nodoc
+mixin _$AgentConfig {
+ String get id => throw _privateConstructorUsedError;
+ String get displayName => throw _privateConstructorUsedError;
+ AgentType get type => throw _privateConstructorUsedError;
+ String get bridgeUrl => throw _privateConstructorUsedError;
+ String get authToken => throw _privateConstructorUsedError;
+ String? get workingDirectory => throw _privateConstructorUsedError;
+ AgentConnectionStatus get status => throw _privateConstructorUsedError;
+ DateTime? get lastConnectedAt => throw _privateConstructorUsedError;
+ DateTime get createdAt => throw _privateConstructorUsedError;
+ DateTime get updatedAt => throw _privateConstructorUsedError;
+
+ /// Serializes this AgentConfig to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of AgentConfig
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $AgentConfigCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $AgentConfigCopyWith<$Res> {
+ factory $AgentConfigCopyWith(
+ AgentConfig value, $Res Function(AgentConfig) then) =
+ _$AgentConfigCopyWithImpl<$Res, AgentConfig>;
+ @useResult
+ $Res call(
+ {String id,
+ String displayName,
+ AgentType type,
+ String bridgeUrl,
+ String authToken,
+ String? workingDirectory,
+ AgentConnectionStatus status,
+ DateTime? lastConnectedAt,
+ DateTime createdAt,
+ DateTime updatedAt});
+}
+
+/// @nodoc
+class _$AgentConfigCopyWithImpl<$Res, $Val extends AgentConfig>
+ implements $AgentConfigCopyWith<$Res> {
+ _$AgentConfigCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of AgentConfig
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? displayName = null,
+ Object? type = null,
+ Object? bridgeUrl = null,
+ Object? authToken = null,
+ Object? workingDirectory = freezed,
+ Object? status = null,
+ Object? lastConnectedAt = freezed,
+ Object? createdAt = null,
+ Object? updatedAt = null,
+ }) {
+ return _then(_value.copyWith(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as String,
+ displayName: null == displayName
+ ? _value.displayName
+ : displayName // ignore: cast_nullable_to_non_nullable
+ as String,
+ type: null == type
+ ? _value.type
+ : type // ignore: cast_nullable_to_non_nullable
+ as AgentType,
+ bridgeUrl: null == bridgeUrl
+ ? _value.bridgeUrl
+ : bridgeUrl // ignore: cast_nullable_to_non_nullable
+ as String,
+ authToken: null == authToken
+ ? _value.authToken
+ : authToken // ignore: cast_nullable_to_non_nullable
+ as String,
+ workingDirectory: freezed == workingDirectory
+ ? _value.workingDirectory
+ : workingDirectory // ignore: cast_nullable_to_non_nullable
+ as String?,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as AgentConnectionStatus,
+ lastConnectedAt: freezed == lastConnectedAt
+ ? _value.lastConnectedAt
+ : lastConnectedAt // ignore: cast_nullable_to_non_nullable
+ as DateTime?,
+ createdAt: null == createdAt
+ ? _value.createdAt
+ : createdAt // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ updatedAt: null == updatedAt
+ ? _value.updatedAt
+ : updatedAt // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$AgentConfigImplCopyWith<$Res>
+ implements $AgentConfigCopyWith<$Res> {
+ factory _$$AgentConfigImplCopyWith(
+ _$AgentConfigImpl value, $Res Function(_$AgentConfigImpl) then) =
+ __$$AgentConfigImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String id,
+ String displayName,
+ AgentType type,
+ String bridgeUrl,
+ String authToken,
+ String? workingDirectory,
+ AgentConnectionStatus status,
+ DateTime? lastConnectedAt,
+ DateTime createdAt,
+ DateTime updatedAt});
+}
+
+/// @nodoc
+class __$$AgentConfigImplCopyWithImpl<$Res>
+ extends _$AgentConfigCopyWithImpl<$Res, _$AgentConfigImpl>
+ implements _$$AgentConfigImplCopyWith<$Res> {
+ __$$AgentConfigImplCopyWithImpl(
+ _$AgentConfigImpl _value, $Res Function(_$AgentConfigImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of AgentConfig
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? id = null,
+ Object? displayName = null,
+ Object? type = null,
+ Object? bridgeUrl = null,
+ Object? authToken = null,
+ Object? workingDirectory = freezed,
+ Object? status = null,
+ Object? lastConnectedAt = freezed,
+ Object? createdAt = null,
+ Object? updatedAt = null,
+ }) {
+ return _then(_$AgentConfigImpl(
+ id: null == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as String,
+ displayName: null == displayName
+ ? _value.displayName
+ : displayName // ignore: cast_nullable_to_non_nullable
+ as String,
+ type: null == type
+ ? _value.type
+ : type // ignore: cast_nullable_to_non_nullable
+ as AgentType,
+ bridgeUrl: null == bridgeUrl
+ ? _value.bridgeUrl
+ : bridgeUrl // ignore: cast_nullable_to_non_nullable
+ as String,
+ authToken: null == authToken
+ ? _value.authToken
+ : authToken // ignore: cast_nullable_to_non_nullable
+ as String,
+ workingDirectory: freezed == workingDirectory
+ ? _value.workingDirectory
+ : workingDirectory // ignore: cast_nullable_to_non_nullable
+ as String?,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as AgentConnectionStatus,
+ lastConnectedAt: freezed == lastConnectedAt
+ ? _value.lastConnectedAt
+ : lastConnectedAt // ignore: cast_nullable_to_non_nullable
+ as DateTime?,
+ createdAt: null == createdAt
+ ? _value.createdAt
+ : createdAt // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ updatedAt: null == updatedAt
+ ? _value.updatedAt
+ : updatedAt // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$AgentConfigImpl implements _AgentConfig {
+ const _$AgentConfigImpl(
+ {required this.id,
+ required this.displayName,
+ required this.type,
+ required this.bridgeUrl,
+ required this.authToken,
+ this.workingDirectory,
+ this.status = AgentConnectionStatus.disconnected,
+ this.lastConnectedAt,
+ required this.createdAt,
+ required this.updatedAt});
+
+ factory _$AgentConfigImpl.fromJson(Map json) =>
+ _$$AgentConfigImplFromJson(json);
+
+ @override
+ final String id;
+ @override
+ final String displayName;
+ @override
+ final AgentType type;
+ @override
+ final String bridgeUrl;
+ @override
+ final String authToken;
+ @override
+ final String? workingDirectory;
+ @override
+ @JsonKey()
+ final AgentConnectionStatus status;
+ @override
+ final DateTime? lastConnectedAt;
+ @override
+ final DateTime createdAt;
+ @override
+ final DateTime updatedAt;
+
+ @override
+ String toString() {
+ return 'AgentConfig(id: $id, displayName: $displayName, type: $type, bridgeUrl: $bridgeUrl, authToken: $authToken, workingDirectory: $workingDirectory, status: $status, lastConnectedAt: $lastConnectedAt, createdAt: $createdAt, updatedAt: $updatedAt)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$AgentConfigImpl &&
+ (identical(other.id, id) || other.id == id) &&
+ (identical(other.displayName, displayName) ||
+ other.displayName == displayName) &&
+ (identical(other.type, type) || other.type == type) &&
+ (identical(other.bridgeUrl, bridgeUrl) ||
+ other.bridgeUrl == bridgeUrl) &&
+ (identical(other.authToken, authToken) ||
+ other.authToken == authToken) &&
+ (identical(other.workingDirectory, workingDirectory) ||
+ other.workingDirectory == workingDirectory) &&
+ (identical(other.status, status) || other.status == status) &&
+ (identical(other.lastConnectedAt, lastConnectedAt) ||
+ other.lastConnectedAt == lastConnectedAt) &&
+ (identical(other.createdAt, createdAt) ||
+ other.createdAt == createdAt) &&
+ (identical(other.updatedAt, updatedAt) ||
+ other.updatedAt == updatedAt));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ id,
+ displayName,
+ type,
+ bridgeUrl,
+ authToken,
+ workingDirectory,
+ status,
+ lastConnectedAt,
+ createdAt,
+ updatedAt);
+
+ /// Create a copy of AgentConfig
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$AgentConfigImplCopyWith<_$AgentConfigImpl> get copyWith =>
+ __$$AgentConfigImplCopyWithImpl<_$AgentConfigImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$AgentConfigImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _AgentConfig implements AgentConfig {
+ const factory _AgentConfig(
+ {required final String id,
+ required final String displayName,
+ required final AgentType type,
+ required final String bridgeUrl,
+ required final String authToken,
+ final String? workingDirectory,
+ final AgentConnectionStatus status,
+ final DateTime? lastConnectedAt,
+ required final DateTime createdAt,
+ required final DateTime updatedAt}) = _$AgentConfigImpl;
+
+ factory _AgentConfig.fromJson(Map json) =
+ _$AgentConfigImpl.fromJson;
+
+ @override
+ String get id;
+ @override
+ String get displayName;
+ @override
+ AgentType get type;
+ @override
+ String get bridgeUrl;
+ @override
+ String get authToken;
+ @override
+ String? get workingDirectory;
+ @override
+ AgentConnectionStatus get status;
+ @override
+ DateTime? get lastConnectedAt;
+ @override
+ DateTime get createdAt;
+ @override
+ DateTime get updatedAt;
+
+ /// Create a copy of AgentConfig
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$AgentConfigImplCopyWith<_$AgentConfigImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/apps/mobile/lib/core/models/agent_models.g.dart b/apps/mobile/lib/core/models/agent_models.g.dart
new file mode 100644
index 0000000..0589662
--- /dev/null
+++ b/apps/mobile/lib/core/models/agent_models.g.dart
@@ -0,0 +1,53 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'agent_models.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$AgentConfigImpl _$$AgentConfigImplFromJson(Map json) =>
+ _$AgentConfigImpl(
+ id: json['id'] as String,
+ displayName: json['displayName'] as String,
+ type: $enumDecode(_$AgentTypeEnumMap, json['type']),
+ bridgeUrl: json['bridgeUrl'] as String,
+ authToken: json['authToken'] as String,
+ workingDirectory: json['workingDirectory'] as String?,
+ status:
+ $enumDecodeNullable(_$AgentConnectionStatusEnumMap, json['status']) ??
+ AgentConnectionStatus.disconnected,
+ lastConnectedAt: json['lastConnectedAt'] == null
+ ? null
+ : DateTime.parse(json['lastConnectedAt'] as String),
+ createdAt: DateTime.parse(json['createdAt'] as String),
+ updatedAt: DateTime.parse(json['updatedAt'] as String),
+ );
+
+Map _$$AgentConfigImplToJson(_$AgentConfigImpl instance) =>
+ {
+ 'id': instance.id,
+ 'displayName': instance.displayName,
+ 'type': _$AgentTypeEnumMap[instance.type]!,
+ 'bridgeUrl': instance.bridgeUrl,
+ 'authToken': instance.authToken,
+ 'workingDirectory': instance.workingDirectory,
+ 'status': _$AgentConnectionStatusEnumMap[instance.status]!,
+ 'lastConnectedAt': instance.lastConnectedAt?.toIso8601String(),
+ 'createdAt': instance.createdAt.toIso8601String(),
+ 'updatedAt': instance.updatedAt.toIso8601String(),
+ };
+
+const _$AgentTypeEnumMap = {
+ AgentType.claudeCode: 'claudeCode',
+ AgentType.openCode: 'openCode',
+ AgentType.aider: 'aider',
+ AgentType.goose: 'goose',
+ AgentType.custom: 'custom',
+};
+
+const _$AgentConnectionStatusEnumMap = {
+ AgentConnectionStatus.connected: 'connected',
+ AgentConnectionStatus.disconnected: 'disconnected',
+ AgentConnectionStatus.inactive: 'inactive',
+};
diff --git a/apps/mobile/lib/core/models/file_models.dart b/apps/mobile/lib/core/models/file_models.dart
new file mode 100644
index 0000000..732cf52
--- /dev/null
+++ b/apps/mobile/lib/core/models/file_models.dart
@@ -0,0 +1,22 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'file_models.freezed.dart';
+part 'file_models.g.dart';
+
+enum FileNodeType { file, directory }
+
+@freezed
+class FileTreeNode with _$FileTreeNode {
+ const factory FileTreeNode({
+ required String name,
+ required String path,
+ required FileNodeType type,
+ List? children,
+ int? size,
+ DateTime? modifiedAt,
+ String? content,
+ }) = _FileTreeNode;
+
+ factory FileTreeNode.fromJson(Map json) =>
+ _$FileTreeNodeFromJson(json);
+}
diff --git a/apps/mobile/lib/core/models/file_models.freezed.dart b/apps/mobile/lib/core/models/file_models.freezed.dart
new file mode 100644
index 0000000..b68bac3
--- /dev/null
+++ b/apps/mobile/lib/core/models/file_models.freezed.dart
@@ -0,0 +1,306 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// 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 'file_models.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+FileTreeNode _$FileTreeNodeFromJson(Map json) {
+ return _FileTreeNode.fromJson(json);
+}
+
+/// @nodoc
+mixin _$FileTreeNode {
+ String get name => throw _privateConstructorUsedError;
+ String get path => throw _privateConstructorUsedError;
+ FileNodeType get type => throw _privateConstructorUsedError;
+ List? get children => throw _privateConstructorUsedError;
+ int? get size => throw _privateConstructorUsedError;
+ DateTime? get modifiedAt => throw _privateConstructorUsedError;
+ String? get content => throw _privateConstructorUsedError;
+
+ /// Serializes this FileTreeNode to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of FileTreeNode
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $FileTreeNodeCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $FileTreeNodeCopyWith<$Res> {
+ factory $FileTreeNodeCopyWith(
+ FileTreeNode value, $Res Function(FileTreeNode) then) =
+ _$FileTreeNodeCopyWithImpl<$Res, FileTreeNode>;
+ @useResult
+ $Res call(
+ {String name,
+ String path,
+ FileNodeType type,
+ List? children,
+ int? size,
+ DateTime? modifiedAt,
+ String? content});
+}
+
+/// @nodoc
+class _$FileTreeNodeCopyWithImpl<$Res, $Val extends FileTreeNode>
+ implements $FileTreeNodeCopyWith<$Res> {
+ _$FileTreeNodeCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of FileTreeNode
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? name = null,
+ Object? path = null,
+ Object? type = null,
+ Object? children = freezed,
+ Object? size = freezed,
+ Object? modifiedAt = freezed,
+ Object? content = freezed,
+ }) {
+ return _then(_value.copyWith(
+ name: null == name
+ ? _value.name
+ : name // ignore: cast_nullable_to_non_nullable
+ as String,
+ path: null == path
+ ? _value.path
+ : path // ignore: cast_nullable_to_non_nullable
+ as String,
+ type: null == type
+ ? _value.type
+ : type // ignore: cast_nullable_to_non_nullable
+ as FileNodeType,
+ children: freezed == children
+ ? _value.children
+ : children // ignore: cast_nullable_to_non_nullable
+ as List?,
+ size: freezed == size
+ ? _value.size
+ : size // ignore: cast_nullable_to_non_nullable
+ as int?,
+ modifiedAt: freezed == modifiedAt
+ ? _value.modifiedAt
+ : modifiedAt // ignore: cast_nullable_to_non_nullable
+ as DateTime?,
+ content: freezed == content
+ ? _value.content
+ : content // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$FileTreeNodeImplCopyWith<$Res>
+ implements $FileTreeNodeCopyWith<$Res> {
+ factory _$$FileTreeNodeImplCopyWith(
+ _$FileTreeNodeImpl value, $Res Function(_$FileTreeNodeImpl) then) =
+ __$$FileTreeNodeImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String name,
+ String path,
+ FileNodeType type,
+ List? children,
+ int? size,
+ DateTime? modifiedAt,
+ String? content});
+}
+
+/// @nodoc
+class __$$FileTreeNodeImplCopyWithImpl<$Res>
+ extends _$FileTreeNodeCopyWithImpl<$Res, _$FileTreeNodeImpl>
+ implements _$$FileTreeNodeImplCopyWith<$Res> {
+ __$$FileTreeNodeImplCopyWithImpl(
+ _$FileTreeNodeImpl _value, $Res Function(_$FileTreeNodeImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of FileTreeNode
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? name = null,
+ Object? path = null,
+ Object? type = null,
+ Object? children = freezed,
+ Object? size = freezed,
+ Object? modifiedAt = freezed,
+ Object? content = freezed,
+ }) {
+ return _then(_$FileTreeNodeImpl(
+ name: null == name
+ ? _value.name
+ : name // ignore: cast_nullable_to_non_nullable
+ as String,
+ path: null == path
+ ? _value.path
+ : path // ignore: cast_nullable_to_non_nullable
+ as String,
+ type: null == type
+ ? _value.type
+ : type // ignore: cast_nullable_to_non_nullable
+ as FileNodeType,
+ children: freezed == children
+ ? _value._children
+ : children // ignore: cast_nullable_to_non_nullable
+ as List?,
+ size: freezed == size
+ ? _value.size
+ : size // ignore: cast_nullable_to_non_nullable
+ as int?,
+ modifiedAt: freezed == modifiedAt
+ ? _value.modifiedAt
+ : modifiedAt // ignore: cast_nullable_to_non_nullable
+ as DateTime?,
+ content: freezed == content
+ ? _value.content
+ : content // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$FileTreeNodeImpl implements _FileTreeNode {
+ const _$FileTreeNodeImpl(
+ {required this.name,
+ required this.path,
+ required this.type,
+ final List? children,
+ this.size,
+ this.modifiedAt,
+ this.content})
+ : _children = children;
+
+ factory _$FileTreeNodeImpl.fromJson(Map json) =>
+ _$$FileTreeNodeImplFromJson(json);
+
+ @override
+ final String name;
+ @override
+ final String path;
+ @override
+ final FileNodeType type;
+ final List? _children;
+ @override
+ List? get children {
+ final value = _children;
+ if (value == null) return null;
+ if (_children is EqualUnmodifiableListView) return _children;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(value);
+ }
+
+ @override
+ final int? size;
+ @override
+ final DateTime? modifiedAt;
+ @override
+ final String? content;
+
+ @override
+ String toString() {
+ return 'FileTreeNode(name: $name, path: $path, type: $type, children: $children, size: $size, modifiedAt: $modifiedAt, content: $content)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$FileTreeNodeImpl &&
+ (identical(other.name, name) || other.name == name) &&
+ (identical(other.path, path) || other.path == path) &&
+ (identical(other.type, type) || other.type == type) &&
+ const DeepCollectionEquality().equals(other._children, _children) &&
+ (identical(other.size, size) || other.size == size) &&
+ (identical(other.modifiedAt, modifiedAt) ||
+ other.modifiedAt == modifiedAt) &&
+ (identical(other.content, content) || other.content == content));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ name,
+ path,
+ type,
+ const DeepCollectionEquality().hash(_children),
+ size,
+ modifiedAt,
+ content);
+
+ /// Create a copy of FileTreeNode
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$FileTreeNodeImplCopyWith<_$FileTreeNodeImpl> get copyWith =>
+ __$$FileTreeNodeImplCopyWithImpl<_$FileTreeNodeImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$FileTreeNodeImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _FileTreeNode implements FileTreeNode {
+ const factory _FileTreeNode(
+ {required final String name,
+ required final String path,
+ required final FileNodeType type,
+ final List? children,
+ final int? size,
+ final DateTime? modifiedAt,
+ final String? content}) = _$FileTreeNodeImpl;
+
+ factory _FileTreeNode.fromJson(Map json) =
+ _$FileTreeNodeImpl.fromJson;
+
+ @override
+ String get name;
+ @override
+ String get path;
+ @override
+ FileNodeType get type;
+ @override
+ List? get children;
+ @override
+ int? get size;
+ @override
+ DateTime? get modifiedAt;
+ @override
+ String? get content;
+
+ /// Create a copy of FileTreeNode
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$FileTreeNodeImplCopyWith<_$FileTreeNodeImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/apps/mobile/lib/core/models/file_models.g.dart b/apps/mobile/lib/core/models/file_models.g.dart
new file mode 100644
index 0000000..d85d2ec
--- /dev/null
+++ b/apps/mobile/lib/core/models/file_models.g.dart
@@ -0,0 +1,38 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'file_models.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$FileTreeNodeImpl _$$FileTreeNodeImplFromJson(Map json) =>
+ _$FileTreeNodeImpl(
+ name: json['name'] as String,
+ path: json['path'] as String,
+ type: $enumDecode(_$FileNodeTypeEnumMap, json['type']),
+ children: (json['children'] as List?)
+ ?.map((e) => FileTreeNode.fromJson(e as Map))
+ .toList(),
+ size: (json['size'] as num?)?.toInt(),
+ modifiedAt: json['modifiedAt'] == null
+ ? null
+ : DateTime.parse(json['modifiedAt'] as String),
+ content: json['content'] as String?,
+ );
+
+Map _$$FileTreeNodeImplToJson(_$FileTreeNodeImpl instance) =>
+ {
+ 'name': instance.name,
+ 'path': instance.path,
+ 'type': _$FileNodeTypeEnumMap[instance.type]!,
+ 'children': instance.children,
+ 'size': instance.size,
+ 'modifiedAt': instance.modifiedAt?.toIso8601String(),
+ 'content': instance.content,
+ };
+
+const _$FileNodeTypeEnumMap = {
+ FileNodeType.file: 'file',
+ FileNodeType.directory: 'directory',
+};
diff --git a/apps/mobile/lib/core/models/git_models.dart b/apps/mobile/lib/core/models/git_models.dart
new file mode 100644
index 0000000..17a66da
--- /dev/null
+++ b/apps/mobile/lib/core/models/git_models.dart
@@ -0,0 +1,96 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'git_models.freezed.dart';
+part 'git_models.g.dart';
+
+enum FileChangeStatus { modified, added, deleted, untracked, renamed }
+
+enum DiffLineType { context, added, removed }
+
+@freezed
+class GitStatus with _$GitStatus {
+ const factory GitStatus({
+ required String branch,
+ required List changes,
+ required int ahead,
+ required int behind,
+ required bool isClean,
+ }) = _GitStatus;
+
+ factory GitStatus.fromJson(Map json) =>
+ _$GitStatusFromJson(json);
+}
+
+@freezed
+class GitFileChange with _$GitFileChange {
+ const factory GitFileChange({
+ required String path,
+ required FileChangeStatus status,
+ int? additions,
+ int? deletions,
+ String? diff,
+ }) = _GitFileChange;
+
+ factory GitFileChange.fromJson(Map json) =>
+ _$GitFileChangeFromJson(json);
+}
+
+@freezed
+class GitBranch with _$GitBranch {
+ const factory GitBranch({
+ required String name,
+ required bool isCurrent,
+ String? upstream,
+ int? ahead,
+ int? behind,
+ }) = _GitBranch;
+
+ factory GitBranch.fromJson(Map json) =>
+ _$GitBranchFromJson(json);
+}
+
+@freezed
+class DiffFile with _$DiffFile {
+ const factory DiffFile({
+ required String path,
+ required String oldPath,
+ required String newPath,
+ required FileChangeStatus status,
+ required int additions,
+ required int deletions,
+ required List hunks,
+ String? oldMode,
+ String? newMode,
+ }) = _DiffFile;
+
+ factory DiffFile.fromJson(Map json) =>
+ _$DiffFileFromJson(json);
+}
+
+@freezed
+class DiffHunk with _$DiffHunk {
+ const factory DiffHunk({
+ required String header,
+ required int oldStart,
+ required int oldLines,
+ required int newStart,
+ required int newLines,
+ required List lines,
+ }) = _DiffHunk;
+
+ factory DiffHunk.fromJson(Map json) =>
+ _$DiffHunkFromJson(json);
+}
+
+@freezed
+class DiffLine with _$DiffLine {
+ const factory DiffLine({
+ required DiffLineType type,
+ required String content,
+ int? oldLineNumber,
+ int? newLineNumber,
+ }) = _DiffLine;
+
+ factory DiffLine.fromJson(Map json) =>
+ _$DiffLineFromJson(json);
+}
diff --git a/apps/mobile/lib/core/models/git_models.freezed.dart b/apps/mobile/lib/core/models/git_models.freezed.dart
new file mode 100644
index 0000000..50d1d10
--- /dev/null
+++ b/apps/mobile/lib/core/models/git_models.freezed.dart
@@ -0,0 +1,1527 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// 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 'git_models.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+GitStatus _$GitStatusFromJson(Map json) {
+ return _GitStatus.fromJson(json);
+}
+
+/// @nodoc
+mixin _$GitStatus {
+ String get branch => throw _privateConstructorUsedError;
+ List get changes => throw _privateConstructorUsedError;
+ int get ahead => throw _privateConstructorUsedError;
+ int get behind => throw _privateConstructorUsedError;
+ bool get isClean => throw _privateConstructorUsedError;
+
+ /// Serializes this GitStatus to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of GitStatus
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $GitStatusCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $GitStatusCopyWith<$Res> {
+ factory $GitStatusCopyWith(GitStatus value, $Res Function(GitStatus) then) =
+ _$GitStatusCopyWithImpl<$Res, GitStatus>;
+ @useResult
+ $Res call(
+ {String branch,
+ List changes,
+ int ahead,
+ int behind,
+ bool isClean});
+}
+
+/// @nodoc
+class _$GitStatusCopyWithImpl<$Res, $Val extends GitStatus>
+ implements $GitStatusCopyWith<$Res> {
+ _$GitStatusCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of GitStatus
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? branch = null,
+ Object? changes = null,
+ Object? ahead = null,
+ Object? behind = null,
+ Object? isClean = null,
+ }) {
+ return _then(_value.copyWith(
+ branch: null == branch
+ ? _value.branch
+ : branch // ignore: cast_nullable_to_non_nullable
+ as String,
+ changes: null == changes
+ ? _value.changes
+ : changes // ignore: cast_nullable_to_non_nullable
+ as List,
+ ahead: null == ahead
+ ? _value.ahead
+ : ahead // ignore: cast_nullable_to_non_nullable
+ as int,
+ behind: null == behind
+ ? _value.behind
+ : behind // ignore: cast_nullable_to_non_nullable
+ as int,
+ isClean: null == isClean
+ ? _value.isClean
+ : isClean // ignore: cast_nullable_to_non_nullable
+ as bool,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$GitStatusImplCopyWith<$Res>
+ implements $GitStatusCopyWith<$Res> {
+ factory _$$GitStatusImplCopyWith(
+ _$GitStatusImpl value, $Res Function(_$GitStatusImpl) then) =
+ __$$GitStatusImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String branch,
+ List changes,
+ int ahead,
+ int behind,
+ bool isClean});
+}
+
+/// @nodoc
+class __$$GitStatusImplCopyWithImpl<$Res>
+ extends _$GitStatusCopyWithImpl<$Res, _$GitStatusImpl>
+ implements _$$GitStatusImplCopyWith<$Res> {
+ __$$GitStatusImplCopyWithImpl(
+ _$GitStatusImpl _value, $Res Function(_$GitStatusImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of GitStatus
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? branch = null,
+ Object? changes = null,
+ Object? ahead = null,
+ Object? behind = null,
+ Object? isClean = null,
+ }) {
+ return _then(_$GitStatusImpl(
+ branch: null == branch
+ ? _value.branch
+ : branch // ignore: cast_nullable_to_non_nullable
+ as String,
+ changes: null == changes
+ ? _value._changes
+ : changes // ignore: cast_nullable_to_non_nullable
+ as List,
+ ahead: null == ahead
+ ? _value.ahead
+ : ahead // ignore: cast_nullable_to_non_nullable
+ as int,
+ behind: null == behind
+ ? _value.behind
+ : behind // ignore: cast_nullable_to_non_nullable
+ as int,
+ isClean: null == isClean
+ ? _value.isClean
+ : isClean // ignore: cast_nullable_to_non_nullable
+ as bool,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$GitStatusImpl implements _GitStatus {
+ const _$GitStatusImpl(
+ {required this.branch,
+ required final List changes,
+ required this.ahead,
+ required this.behind,
+ required this.isClean})
+ : _changes = changes;
+
+ factory _$GitStatusImpl.fromJson(Map json) =>
+ _$$GitStatusImplFromJson(json);
+
+ @override
+ final String branch;
+ final List _changes;
+ @override
+ List get changes {
+ if (_changes is EqualUnmodifiableListView) return _changes;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_changes);
+ }
+
+ @override
+ final int ahead;
+ @override
+ final int behind;
+ @override
+ final bool isClean;
+
+ @override
+ String toString() {
+ return 'GitStatus(branch: $branch, changes: $changes, ahead: $ahead, behind: $behind, isClean: $isClean)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$GitStatusImpl &&
+ (identical(other.branch, branch) || other.branch == branch) &&
+ const DeepCollectionEquality().equals(other._changes, _changes) &&
+ (identical(other.ahead, ahead) || other.ahead == ahead) &&
+ (identical(other.behind, behind) || other.behind == behind) &&
+ (identical(other.isClean, isClean) || other.isClean == isClean));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, branch,
+ const DeepCollectionEquality().hash(_changes), ahead, behind, isClean);
+
+ /// Create a copy of GitStatus
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$GitStatusImplCopyWith<_$GitStatusImpl> get copyWith =>
+ __$$GitStatusImplCopyWithImpl<_$GitStatusImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$GitStatusImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _GitStatus implements GitStatus {
+ const factory _GitStatus(
+ {required final String branch,
+ required final List changes,
+ required final int ahead,
+ required final int behind,
+ required final bool isClean}) = _$GitStatusImpl;
+
+ factory _GitStatus.fromJson(Map json) =
+ _$GitStatusImpl.fromJson;
+
+ @override
+ String get branch;
+ @override
+ List get changes;
+ @override
+ int get ahead;
+ @override
+ int get behind;
+ @override
+ bool get isClean;
+
+ /// Create a copy of GitStatus
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$GitStatusImplCopyWith<_$GitStatusImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+GitFileChange _$GitFileChangeFromJson(Map json) {
+ return _GitFileChange.fromJson(json);
+}
+
+/// @nodoc
+mixin _$GitFileChange {
+ String get path => throw _privateConstructorUsedError;
+ FileChangeStatus get status => throw _privateConstructorUsedError;
+ int? get additions => throw _privateConstructorUsedError;
+ int? get deletions => throw _privateConstructorUsedError;
+ String? get diff => throw _privateConstructorUsedError;
+
+ /// Serializes this GitFileChange to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of GitFileChange
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $GitFileChangeCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $GitFileChangeCopyWith<$Res> {
+ factory $GitFileChangeCopyWith(
+ GitFileChange value, $Res Function(GitFileChange) then) =
+ _$GitFileChangeCopyWithImpl<$Res, GitFileChange>;
+ @useResult
+ $Res call(
+ {String path,
+ FileChangeStatus status,
+ int? additions,
+ int? deletions,
+ String? diff});
+}
+
+/// @nodoc
+class _$GitFileChangeCopyWithImpl<$Res, $Val extends GitFileChange>
+ implements $GitFileChangeCopyWith<$Res> {
+ _$GitFileChangeCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of GitFileChange
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? path = null,
+ Object? status = null,
+ Object? additions = freezed,
+ Object? deletions = freezed,
+ Object? diff = freezed,
+ }) {
+ return _then(_value.copyWith(
+ path: null == path
+ ? _value.path
+ : path // ignore: cast_nullable_to_non_nullable
+ as String,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as FileChangeStatus,
+ additions: freezed == additions
+ ? _value.additions
+ : additions // ignore: cast_nullable_to_non_nullable
+ as int?,
+ deletions: freezed == deletions
+ ? _value.deletions
+ : deletions // ignore: cast_nullable_to_non_nullable
+ as int?,
+ diff: freezed == diff
+ ? _value.diff
+ : diff // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$GitFileChangeImplCopyWith<$Res>
+ implements $GitFileChangeCopyWith<$Res> {
+ factory _$$GitFileChangeImplCopyWith(
+ _$GitFileChangeImpl value, $Res Function(_$GitFileChangeImpl) then) =
+ __$$GitFileChangeImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String path,
+ FileChangeStatus status,
+ int? additions,
+ int? deletions,
+ String? diff});
+}
+
+/// @nodoc
+class __$$GitFileChangeImplCopyWithImpl<$Res>
+ extends _$GitFileChangeCopyWithImpl<$Res, _$GitFileChangeImpl>
+ implements _$$GitFileChangeImplCopyWith<$Res> {
+ __$$GitFileChangeImplCopyWithImpl(
+ _$GitFileChangeImpl _value, $Res Function(_$GitFileChangeImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of GitFileChange
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? path = null,
+ Object? status = null,
+ Object? additions = freezed,
+ Object? deletions = freezed,
+ Object? diff = freezed,
+ }) {
+ return _then(_$GitFileChangeImpl(
+ path: null == path
+ ? _value.path
+ : path // ignore: cast_nullable_to_non_nullable
+ as String,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as FileChangeStatus,
+ additions: freezed == additions
+ ? _value.additions
+ : additions // ignore: cast_nullable_to_non_nullable
+ as int?,
+ deletions: freezed == deletions
+ ? _value.deletions
+ : deletions // ignore: cast_nullable_to_non_nullable
+ as int?,
+ diff: freezed == diff
+ ? _value.diff
+ : diff // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$GitFileChangeImpl implements _GitFileChange {
+ const _$GitFileChangeImpl(
+ {required this.path,
+ required this.status,
+ this.additions,
+ this.deletions,
+ this.diff});
+
+ factory _$GitFileChangeImpl.fromJson(Map json) =>
+ _$$GitFileChangeImplFromJson(json);
+
+ @override
+ final String path;
+ @override
+ final FileChangeStatus status;
+ @override
+ final int? additions;
+ @override
+ final int? deletions;
+ @override
+ final String? diff;
+
+ @override
+ String toString() {
+ return 'GitFileChange(path: $path, status: $status, additions: $additions, deletions: $deletions, diff: $diff)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$GitFileChangeImpl &&
+ (identical(other.path, path) || other.path == path) &&
+ (identical(other.status, status) || other.status == status) &&
+ (identical(other.additions, additions) ||
+ other.additions == additions) &&
+ (identical(other.deletions, deletions) ||
+ other.deletions == deletions) &&
+ (identical(other.diff, diff) || other.diff == diff));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode =>
+ Object.hash(runtimeType, path, status, additions, deletions, diff);
+
+ /// Create a copy of GitFileChange
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$GitFileChangeImplCopyWith<_$GitFileChangeImpl> get copyWith =>
+ __$$GitFileChangeImplCopyWithImpl<_$GitFileChangeImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$GitFileChangeImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _GitFileChange implements GitFileChange {
+ const factory _GitFileChange(
+ {required final String path,
+ required final FileChangeStatus status,
+ final int? additions,
+ final int? deletions,
+ final String? diff}) = _$GitFileChangeImpl;
+
+ factory _GitFileChange.fromJson(Map json) =
+ _$GitFileChangeImpl.fromJson;
+
+ @override
+ String get path;
+ @override
+ FileChangeStatus get status;
+ @override
+ int? get additions;
+ @override
+ int? get deletions;
+ @override
+ String? get diff;
+
+ /// Create a copy of GitFileChange
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$GitFileChangeImplCopyWith<_$GitFileChangeImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+GitBranch _$GitBranchFromJson(Map json) {
+ return _GitBranch.fromJson(json);
+}
+
+/// @nodoc
+mixin _$GitBranch {
+ String get name => throw _privateConstructorUsedError;
+ bool get isCurrent => throw _privateConstructorUsedError;
+ String? get upstream => throw _privateConstructorUsedError;
+ int? get ahead => throw _privateConstructorUsedError;
+ int? get behind => throw _privateConstructorUsedError;
+
+ /// Serializes this GitBranch to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of GitBranch
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $GitBranchCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $GitBranchCopyWith<$Res> {
+ factory $GitBranchCopyWith(GitBranch value, $Res Function(GitBranch) then) =
+ _$GitBranchCopyWithImpl<$Res, GitBranch>;
+ @useResult
+ $Res call(
+ {String name, bool isCurrent, String? upstream, int? ahead, int? behind});
+}
+
+/// @nodoc
+class _$GitBranchCopyWithImpl<$Res, $Val extends GitBranch>
+ implements $GitBranchCopyWith<$Res> {
+ _$GitBranchCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of GitBranch
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? name = null,
+ Object? isCurrent = null,
+ Object? upstream = freezed,
+ Object? ahead = freezed,
+ Object? behind = freezed,
+ }) {
+ return _then(_value.copyWith(
+ name: null == name
+ ? _value.name
+ : name // ignore: cast_nullable_to_non_nullable
+ as String,
+ isCurrent: null == isCurrent
+ ? _value.isCurrent
+ : isCurrent // ignore: cast_nullable_to_non_nullable
+ as bool,
+ upstream: freezed == upstream
+ ? _value.upstream
+ : upstream // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ahead: freezed == ahead
+ ? _value.ahead
+ : ahead // ignore: cast_nullable_to_non_nullable
+ as int?,
+ behind: freezed == behind
+ ? _value.behind
+ : behind // ignore: cast_nullable_to_non_nullable
+ as int?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$GitBranchImplCopyWith<$Res>
+ implements $GitBranchCopyWith<$Res> {
+ factory _$$GitBranchImplCopyWith(
+ _$GitBranchImpl value, $Res Function(_$GitBranchImpl) then) =
+ __$$GitBranchImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String name, bool isCurrent, String? upstream, int? ahead, int? behind});
+}
+
+/// @nodoc
+class __$$GitBranchImplCopyWithImpl<$Res>
+ extends _$GitBranchCopyWithImpl<$Res, _$GitBranchImpl>
+ implements _$$GitBranchImplCopyWith<$Res> {
+ __$$GitBranchImplCopyWithImpl(
+ _$GitBranchImpl _value, $Res Function(_$GitBranchImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of GitBranch
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? name = null,
+ Object? isCurrent = null,
+ Object? upstream = freezed,
+ Object? ahead = freezed,
+ Object? behind = freezed,
+ }) {
+ return _then(_$GitBranchImpl(
+ name: null == name
+ ? _value.name
+ : name // ignore: cast_nullable_to_non_nullable
+ as String,
+ isCurrent: null == isCurrent
+ ? _value.isCurrent
+ : isCurrent // ignore: cast_nullable_to_non_nullable
+ as bool,
+ upstream: freezed == upstream
+ ? _value.upstream
+ : upstream // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ahead: freezed == ahead
+ ? _value.ahead
+ : ahead // ignore: cast_nullable_to_non_nullable
+ as int?,
+ behind: freezed == behind
+ ? _value.behind
+ : behind // ignore: cast_nullable_to_non_nullable
+ as int?,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$GitBranchImpl implements _GitBranch {
+ const _$GitBranchImpl(
+ {required this.name,
+ required this.isCurrent,
+ this.upstream,
+ this.ahead,
+ this.behind});
+
+ factory _$GitBranchImpl.fromJson(Map json) =>
+ _$$GitBranchImplFromJson(json);
+
+ @override
+ final String name;
+ @override
+ final bool isCurrent;
+ @override
+ final String? upstream;
+ @override
+ final int? ahead;
+ @override
+ final int? behind;
+
+ @override
+ String toString() {
+ return 'GitBranch(name: $name, isCurrent: $isCurrent, upstream: $upstream, ahead: $ahead, behind: $behind)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$GitBranchImpl &&
+ (identical(other.name, name) || other.name == name) &&
+ (identical(other.isCurrent, isCurrent) ||
+ other.isCurrent == isCurrent) &&
+ (identical(other.upstream, upstream) ||
+ other.upstream == upstream) &&
+ (identical(other.ahead, ahead) || other.ahead == ahead) &&
+ (identical(other.behind, behind) || other.behind == behind));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode =>
+ Object.hash(runtimeType, name, isCurrent, upstream, ahead, behind);
+
+ /// Create a copy of GitBranch
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$GitBranchImplCopyWith<_$GitBranchImpl> get copyWith =>
+ __$$GitBranchImplCopyWithImpl<_$GitBranchImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$GitBranchImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _GitBranch implements GitBranch {
+ const factory _GitBranch(
+ {required final String name,
+ required final bool isCurrent,
+ final String? upstream,
+ final int? ahead,
+ final int? behind}) = _$GitBranchImpl;
+
+ factory _GitBranch.fromJson(Map json) =
+ _$GitBranchImpl.fromJson;
+
+ @override
+ String get name;
+ @override
+ bool get isCurrent;
+ @override
+ String? get upstream;
+ @override
+ int? get ahead;
+ @override
+ int? get behind;
+
+ /// Create a copy of GitBranch
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$GitBranchImplCopyWith<_$GitBranchImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+DiffFile _$DiffFileFromJson(Map json) {
+ return _DiffFile.fromJson(json);
+}
+
+/// @nodoc
+mixin _$DiffFile {
+ String get path => throw _privateConstructorUsedError;
+ String get oldPath => throw _privateConstructorUsedError;
+ String get newPath => throw _privateConstructorUsedError;
+ FileChangeStatus get status => throw _privateConstructorUsedError;
+ int get additions => throw _privateConstructorUsedError;
+ int get deletions => throw _privateConstructorUsedError;
+ List get hunks => throw _privateConstructorUsedError;
+ String? get oldMode => throw _privateConstructorUsedError;
+ String? get newMode => throw _privateConstructorUsedError;
+
+ /// Serializes this DiffFile to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of DiffFile
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $DiffFileCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $DiffFileCopyWith<$Res> {
+ factory $DiffFileCopyWith(DiffFile value, $Res Function(DiffFile) then) =
+ _$DiffFileCopyWithImpl<$Res, DiffFile>;
+ @useResult
+ $Res call(
+ {String path,
+ String oldPath,
+ String newPath,
+ FileChangeStatus status,
+ int additions,
+ int deletions,
+ List hunks,
+ String? oldMode,
+ String? newMode});
+}
+
+/// @nodoc
+class _$DiffFileCopyWithImpl<$Res, $Val extends DiffFile>
+ implements $DiffFileCopyWith<$Res> {
+ _$DiffFileCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of DiffFile
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? path = null,
+ Object? oldPath = null,
+ Object? newPath = null,
+ Object? status = null,
+ Object? additions = null,
+ Object? deletions = null,
+ Object? hunks = null,
+ Object? oldMode = freezed,
+ Object? newMode = freezed,
+ }) {
+ return _then(_value.copyWith(
+ path: null == path
+ ? _value.path
+ : path // ignore: cast_nullable_to_non_nullable
+ as String,
+ oldPath: null == oldPath
+ ? _value.oldPath
+ : oldPath // ignore: cast_nullable_to_non_nullable
+ as String,
+ newPath: null == newPath
+ ? _value.newPath
+ : newPath // ignore: cast_nullable_to_non_nullable
+ as String,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as FileChangeStatus,
+ additions: null == additions
+ ? _value.additions
+ : additions // ignore: cast_nullable_to_non_nullable
+ as int,
+ deletions: null == deletions
+ ? _value.deletions
+ : deletions // ignore: cast_nullable_to_non_nullable
+ as int,
+ hunks: null == hunks
+ ? _value.hunks
+ : hunks // ignore: cast_nullable_to_non_nullable
+ as List,
+ oldMode: freezed == oldMode
+ ? _value.oldMode
+ : oldMode // ignore: cast_nullable_to_non_nullable
+ as String?,
+ newMode: freezed == newMode
+ ? _value.newMode
+ : newMode // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$DiffFileImplCopyWith<$Res>
+ implements $DiffFileCopyWith<$Res> {
+ factory _$$DiffFileImplCopyWith(
+ _$DiffFileImpl value, $Res Function(_$DiffFileImpl) then) =
+ __$$DiffFileImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String path,
+ String oldPath,
+ String newPath,
+ FileChangeStatus status,
+ int additions,
+ int deletions,
+ List hunks,
+ String? oldMode,
+ String? newMode});
+}
+
+/// @nodoc
+class __$$DiffFileImplCopyWithImpl<$Res>
+ extends _$DiffFileCopyWithImpl<$Res, _$DiffFileImpl>
+ implements _$$DiffFileImplCopyWith<$Res> {
+ __$$DiffFileImplCopyWithImpl(
+ _$DiffFileImpl _value, $Res Function(_$DiffFileImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of DiffFile
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? path = null,
+ Object? oldPath = null,
+ Object? newPath = null,
+ Object? status = null,
+ Object? additions = null,
+ Object? deletions = null,
+ Object? hunks = null,
+ Object? oldMode = freezed,
+ Object? newMode = freezed,
+ }) {
+ return _then(_$DiffFileImpl(
+ path: null == path
+ ? _value.path
+ : path // ignore: cast_nullable_to_non_nullable
+ as String,
+ oldPath: null == oldPath
+ ? _value.oldPath
+ : oldPath // ignore: cast_nullable_to_non_nullable
+ as String,
+ newPath: null == newPath
+ ? _value.newPath
+ : newPath // ignore: cast_nullable_to_non_nullable
+ as String,
+ status: null == status
+ ? _value.status
+ : status // ignore: cast_nullable_to_non_nullable
+ as FileChangeStatus,
+ additions: null == additions
+ ? _value.additions
+ : additions // ignore: cast_nullable_to_non_nullable
+ as int,
+ deletions: null == deletions
+ ? _value.deletions
+ : deletions // ignore: cast_nullable_to_non_nullable
+ as int,
+ hunks: null == hunks
+ ? _value._hunks
+ : hunks // ignore: cast_nullable_to_non_nullable
+ as List,
+ oldMode: freezed == oldMode
+ ? _value.oldMode
+ : oldMode // ignore: cast_nullable_to_non_nullable
+ as String?,
+ newMode: freezed == newMode
+ ? _value.newMode
+ : newMode // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$DiffFileImpl implements _DiffFile {
+ const _$DiffFileImpl(
+ {required this.path,
+ required this.oldPath,
+ required this.newPath,
+ required this.status,
+ required this.additions,
+ required this.deletions,
+ required final List hunks,
+ this.oldMode,
+ this.newMode})
+ : _hunks = hunks;
+
+ factory _$DiffFileImpl.fromJson(Map json) =>
+ _$$DiffFileImplFromJson(json);
+
+ @override
+ final String path;
+ @override
+ final String oldPath;
+ @override
+ final String newPath;
+ @override
+ final FileChangeStatus status;
+ @override
+ final int additions;
+ @override
+ final int deletions;
+ final List _hunks;
+ @override
+ List get hunks {
+ if (_hunks is EqualUnmodifiableListView) return _hunks;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_hunks);
+ }
+
+ @override
+ final String? oldMode;
+ @override
+ final String? newMode;
+
+ @override
+ String toString() {
+ return 'DiffFile(path: $path, oldPath: $oldPath, newPath: $newPath, status: $status, additions: $additions, deletions: $deletions, hunks: $hunks, oldMode: $oldMode, newMode: $newMode)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$DiffFileImpl &&
+ (identical(other.path, path) || other.path == path) &&
+ (identical(other.oldPath, oldPath) || other.oldPath == oldPath) &&
+ (identical(other.newPath, newPath) || other.newPath == newPath) &&
+ (identical(other.status, status) || other.status == status) &&
+ (identical(other.additions, additions) ||
+ other.additions == additions) &&
+ (identical(other.deletions, deletions) ||
+ other.deletions == deletions) &&
+ const DeepCollectionEquality().equals(other._hunks, _hunks) &&
+ (identical(other.oldMode, oldMode) || other.oldMode == oldMode) &&
+ (identical(other.newMode, newMode) || other.newMode == newMode));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ path,
+ oldPath,
+ newPath,
+ status,
+ additions,
+ deletions,
+ const DeepCollectionEquality().hash(_hunks),
+ oldMode,
+ newMode);
+
+ /// Create a copy of DiffFile
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$DiffFileImplCopyWith<_$DiffFileImpl> get copyWith =>
+ __$$DiffFileImplCopyWithImpl<_$DiffFileImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$DiffFileImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _DiffFile implements DiffFile {
+ const factory _DiffFile(
+ {required final String path,
+ required final String oldPath,
+ required final String newPath,
+ required final FileChangeStatus status,
+ required final int additions,
+ required final int deletions,
+ required final List hunks,
+ final String? oldMode,
+ final String? newMode}) = _$DiffFileImpl;
+
+ factory _DiffFile.fromJson(Map json) =
+ _$DiffFileImpl.fromJson;
+
+ @override
+ String get path;
+ @override
+ String get oldPath;
+ @override
+ String get newPath;
+ @override
+ FileChangeStatus get status;
+ @override
+ int get additions;
+ @override
+ int get deletions;
+ @override
+ List get hunks;
+ @override
+ String? get oldMode;
+ @override
+ String? get newMode;
+
+ /// Create a copy of DiffFile
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$DiffFileImplCopyWith<_$DiffFileImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+DiffHunk _$DiffHunkFromJson(Map json) {
+ return _DiffHunk.fromJson(json);
+}
+
+/// @nodoc
+mixin _$DiffHunk {
+ String get header => throw _privateConstructorUsedError;
+ int get oldStart => throw _privateConstructorUsedError;
+ int get oldLines => throw _privateConstructorUsedError;
+ int get newStart => throw _privateConstructorUsedError;
+ int get newLines => throw _privateConstructorUsedError;
+ List get lines => throw _privateConstructorUsedError;
+
+ /// Serializes this DiffHunk to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of DiffHunk
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $DiffHunkCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $DiffHunkCopyWith<$Res> {
+ factory $DiffHunkCopyWith(DiffHunk value, $Res Function(DiffHunk) then) =
+ _$DiffHunkCopyWithImpl<$Res, DiffHunk>;
+ @useResult
+ $Res call(
+ {String header,
+ int oldStart,
+ int oldLines,
+ int newStart,
+ int newLines,
+ List lines});
+}
+
+/// @nodoc
+class _$DiffHunkCopyWithImpl<$Res, $Val extends DiffHunk>
+ implements $DiffHunkCopyWith<$Res> {
+ _$DiffHunkCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of DiffHunk
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? header = null,
+ Object? oldStart = null,
+ Object? oldLines = null,
+ Object? newStart = null,
+ Object? newLines = null,
+ Object? lines = null,
+ }) {
+ return _then(_value.copyWith(
+ header: null == header
+ ? _value.header
+ : header // ignore: cast_nullable_to_non_nullable
+ as String,
+ oldStart: null == oldStart
+ ? _value.oldStart
+ : oldStart // ignore: cast_nullable_to_non_nullable
+ as int,
+ oldLines: null == oldLines
+ ? _value.oldLines
+ : oldLines // ignore: cast_nullable_to_non_nullable
+ as int,
+ newStart: null == newStart
+ ? _value.newStart
+ : newStart // ignore: cast_nullable_to_non_nullable
+ as int,
+ newLines: null == newLines
+ ? _value.newLines
+ : newLines // ignore: cast_nullable_to_non_nullable
+ as int,
+ lines: null == lines
+ ? _value.lines
+ : lines // ignore: cast_nullable_to_non_nullable
+ as List,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$DiffHunkImplCopyWith<$Res>
+ implements $DiffHunkCopyWith<$Res> {
+ factory _$$DiffHunkImplCopyWith(
+ _$DiffHunkImpl value, $Res Function(_$DiffHunkImpl) then) =
+ __$$DiffHunkImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String header,
+ int oldStart,
+ int oldLines,
+ int newStart,
+ int newLines,
+ List lines});
+}
+
+/// @nodoc
+class __$$DiffHunkImplCopyWithImpl<$Res>
+ extends _$DiffHunkCopyWithImpl<$Res, _$DiffHunkImpl>
+ implements _$$DiffHunkImplCopyWith<$Res> {
+ __$$DiffHunkImplCopyWithImpl(
+ _$DiffHunkImpl _value, $Res Function(_$DiffHunkImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of DiffHunk
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? header = null,
+ Object? oldStart = null,
+ Object? oldLines = null,
+ Object? newStart = null,
+ Object? newLines = null,
+ Object? lines = null,
+ }) {
+ return _then(_$DiffHunkImpl(
+ header: null == header
+ ? _value.header
+ : header // ignore: cast_nullable_to_non_nullable
+ as String,
+ oldStart: null == oldStart
+ ? _value.oldStart
+ : oldStart // ignore: cast_nullable_to_non_nullable
+ as int,
+ oldLines: null == oldLines
+ ? _value.oldLines
+ : oldLines // ignore: cast_nullable_to_non_nullable
+ as int,
+ newStart: null == newStart
+ ? _value.newStart
+ : newStart // ignore: cast_nullable_to_non_nullable
+ as int,
+ newLines: null == newLines
+ ? _value.newLines
+ : newLines // ignore: cast_nullable_to_non_nullable
+ as int,
+ lines: null == lines
+ ? _value._lines
+ : lines // ignore: cast_nullable_to_non_nullable
+ as List,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$DiffHunkImpl implements _DiffHunk {
+ const _$DiffHunkImpl(
+ {required this.header,
+ required this.oldStart,
+ required this.oldLines,
+ required this.newStart,
+ required this.newLines,
+ required final List lines})
+ : _lines = lines;
+
+ factory _$DiffHunkImpl.fromJson(Map json) =>
+ _$$DiffHunkImplFromJson(json);
+
+ @override
+ final String header;
+ @override
+ final int oldStart;
+ @override
+ final int oldLines;
+ @override
+ final int newStart;
+ @override
+ final int newLines;
+ final List _lines;
+ @override
+ List get lines {
+ if (_lines is EqualUnmodifiableListView) return _lines;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(_lines);
+ }
+
+ @override
+ String toString() {
+ return 'DiffHunk(header: $header, oldStart: $oldStart, oldLines: $oldLines, newStart: $newStart, newLines: $newLines, lines: $lines)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$DiffHunkImpl &&
+ (identical(other.header, header) || other.header == header) &&
+ (identical(other.oldStart, oldStart) ||
+ other.oldStart == oldStart) &&
+ (identical(other.oldLines, oldLines) ||
+ other.oldLines == oldLines) &&
+ (identical(other.newStart, newStart) ||
+ other.newStart == newStart) &&
+ (identical(other.newLines, newLines) ||
+ other.newLines == newLines) &&
+ const DeepCollectionEquality().equals(other._lines, _lines));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, header, oldStart, oldLines,
+ newStart, newLines, const DeepCollectionEquality().hash(_lines));
+
+ /// Create a copy of DiffHunk
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$DiffHunkImplCopyWith<_$DiffHunkImpl> get copyWith =>
+ __$$DiffHunkImplCopyWithImpl<_$DiffHunkImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$DiffHunkImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _DiffHunk implements DiffHunk {
+ const factory _DiffHunk(
+ {required final String header,
+ required final int oldStart,
+ required final int oldLines,
+ required final int newStart,
+ required final int newLines,
+ required final List lines}) = _$DiffHunkImpl;
+
+ factory _DiffHunk.fromJson(Map json) =
+ _$DiffHunkImpl.fromJson;
+
+ @override
+ String get header;
+ @override
+ int get oldStart;
+ @override
+ int get oldLines;
+ @override
+ int get newStart;
+ @override
+ int get newLines;
+ @override
+ List get lines;
+
+ /// Create a copy of DiffHunk
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$DiffHunkImplCopyWith<_$DiffHunkImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+DiffLine _$DiffLineFromJson(Map json) {
+ return _DiffLine.fromJson(json);
+}
+
+/// @nodoc
+mixin _$DiffLine {
+ DiffLineType get type => throw _privateConstructorUsedError;
+ String get content => throw _privateConstructorUsedError;
+ int? get oldLineNumber => throw _privateConstructorUsedError;
+ int? get newLineNumber => throw _privateConstructorUsedError;
+
+ /// Serializes this DiffLine to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of DiffLine
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $DiffLineCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $DiffLineCopyWith<$Res> {
+ factory $DiffLineCopyWith(DiffLine value, $Res Function(DiffLine) then) =
+ _$DiffLineCopyWithImpl<$Res, DiffLine>;
+ @useResult
+ $Res call(
+ {DiffLineType type,
+ String content,
+ int? oldLineNumber,
+ int? newLineNumber});
+}
+
+/// @nodoc
+class _$DiffLineCopyWithImpl<$Res, $Val extends DiffLine>
+ implements $DiffLineCopyWith<$Res> {
+ _$DiffLineCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of DiffLine
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? type = null,
+ Object? content = null,
+ Object? oldLineNumber = freezed,
+ Object? newLineNumber = freezed,
+ }) {
+ return _then(_value.copyWith(
+ type: null == type
+ ? _value.type
+ : type // ignore: cast_nullable_to_non_nullable
+ as DiffLineType,
+ content: null == content
+ ? _value.content
+ : content // ignore: cast_nullable_to_non_nullable
+ as String,
+ oldLineNumber: freezed == oldLineNumber
+ ? _value.oldLineNumber
+ : oldLineNumber // ignore: cast_nullable_to_non_nullable
+ as int?,
+ newLineNumber: freezed == newLineNumber
+ ? _value.newLineNumber
+ : newLineNumber // ignore: cast_nullable_to_non_nullable
+ as int?,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$DiffLineImplCopyWith<$Res>
+ implements $DiffLineCopyWith<$Res> {
+ factory _$$DiffLineImplCopyWith(
+ _$DiffLineImpl value, $Res Function(_$DiffLineImpl) then) =
+ __$$DiffLineImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {DiffLineType type,
+ String content,
+ int? oldLineNumber,
+ int? newLineNumber});
+}
+
+/// @nodoc
+class __$$DiffLineImplCopyWithImpl<$Res>
+ extends _$DiffLineCopyWithImpl<$Res, _$DiffLineImpl>
+ implements _$$DiffLineImplCopyWith<$Res> {
+ __$$DiffLineImplCopyWithImpl(
+ _$DiffLineImpl _value, $Res Function(_$DiffLineImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of DiffLine
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? type = null,
+ Object? content = null,
+ Object? oldLineNumber = freezed,
+ Object? newLineNumber = freezed,
+ }) {
+ return _then(_$DiffLineImpl(
+ type: null == type
+ ? _value.type
+ : type // ignore: cast_nullable_to_non_nullable
+ as DiffLineType,
+ content: null == content
+ ? _value.content
+ : content // ignore: cast_nullable_to_non_nullable
+ as String,
+ oldLineNumber: freezed == oldLineNumber
+ ? _value.oldLineNumber
+ : oldLineNumber // ignore: cast_nullable_to_non_nullable
+ as int?,
+ newLineNumber: freezed == newLineNumber
+ ? _value.newLineNumber
+ : newLineNumber // ignore: cast_nullable_to_non_nullable
+ as int?,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$DiffLineImpl implements _DiffLine {
+ const _$DiffLineImpl(
+ {required this.type,
+ required this.content,
+ this.oldLineNumber,
+ this.newLineNumber});
+
+ factory _$DiffLineImpl.fromJson(Map json) =>
+ _$$DiffLineImplFromJson(json);
+
+ @override
+ final DiffLineType type;
+ @override
+ final String content;
+ @override
+ final int? oldLineNumber;
+ @override
+ final int? newLineNumber;
+
+ @override
+ String toString() {
+ return 'DiffLine(type: $type, content: $content, oldLineNumber: $oldLineNumber, newLineNumber: $newLineNumber)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$DiffLineImpl &&
+ (identical(other.type, type) || other.type == type) &&
+ (identical(other.content, content) || other.content == content) &&
+ (identical(other.oldLineNumber, oldLineNumber) ||
+ other.oldLineNumber == oldLineNumber) &&
+ (identical(other.newLineNumber, newLineNumber) ||
+ other.newLineNumber == newLineNumber));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode =>
+ Object.hash(runtimeType, type, content, oldLineNumber, newLineNumber);
+
+ /// Create a copy of DiffLine
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$DiffLineImplCopyWith<_$DiffLineImpl> get copyWith =>
+ __$$DiffLineImplCopyWithImpl<_$DiffLineImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$DiffLineImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _DiffLine implements DiffLine {
+ const factory _DiffLine(
+ {required final DiffLineType type,
+ required final String content,
+ final int? oldLineNumber,
+ final int? newLineNumber}) = _$DiffLineImpl;
+
+ factory _DiffLine.fromJson(Map json) =
+ _$DiffLineImpl.fromJson;
+
+ @override
+ DiffLineType get type;
+ @override
+ String get content;
+ @override
+ int? get oldLineNumber;
+ @override
+ int? get newLineNumber;
+
+ /// Create a copy of DiffLine
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$DiffLineImplCopyWith<_$DiffLineImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/apps/mobile/lib/core/models/git_models.g.dart b/apps/mobile/lib/core/models/git_models.g.dart
new file mode 100644
index 0000000..5c38070
--- /dev/null
+++ b/apps/mobile/lib/core/models/git_models.g.dart
@@ -0,0 +1,143 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'git_models.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$GitStatusImpl _$$GitStatusImplFromJson(Map json) =>
+ _$GitStatusImpl(
+ branch: json['branch'] as String,
+ changes: (json['changes'] as List)
+ .map((e) => GitFileChange.fromJson(e as Map))
+ .toList(),
+ ahead: (json['ahead'] as num).toInt(),
+ behind: (json['behind'] as num).toInt(),
+ isClean: json['isClean'] as bool,
+ );
+
+Map _$$GitStatusImplToJson(_$GitStatusImpl instance) =>
+ {
+ 'branch': instance.branch,
+ 'changes': instance.changes,
+ 'ahead': instance.ahead,
+ 'behind': instance.behind,
+ 'isClean': instance.isClean,
+ };
+
+_$GitFileChangeImpl _$$GitFileChangeImplFromJson(Map json) =>
+ _$GitFileChangeImpl(
+ path: json['path'] as String,
+ status: $enumDecode(_$FileChangeStatusEnumMap, json['status']),
+ additions: (json['additions'] as num?)?.toInt(),
+ deletions: (json['deletions'] as num?)?.toInt(),
+ diff: json['diff'] as String?,
+ );
+
+Map _$$GitFileChangeImplToJson(_$GitFileChangeImpl instance) =>
+ {
+ 'path': instance.path,
+ 'status': _$FileChangeStatusEnumMap[instance.status]!,
+ 'additions': instance.additions,
+ 'deletions': instance.deletions,
+ 'diff': instance.diff,
+ };
+
+const _$FileChangeStatusEnumMap = {
+ FileChangeStatus.modified: 'modified',
+ FileChangeStatus.added: 'added',
+ FileChangeStatus.deleted: 'deleted',
+ FileChangeStatus.untracked: 'untracked',
+ FileChangeStatus.renamed: 'renamed',
+};
+
+_$GitBranchImpl _$$GitBranchImplFromJson(Map json) =>
+ _$GitBranchImpl(
+ name: json['name'] as String,
+ isCurrent: json['isCurrent'] as bool,
+ upstream: json['upstream'] as String?,
+ ahead: (json['ahead'] as num?)?.toInt(),
+ behind: (json['behind'] as num?)?.toInt(),
+ );
+
+Map _$$GitBranchImplToJson(_$GitBranchImpl instance) =>
+ {
+ 'name': instance.name,
+ 'isCurrent': instance.isCurrent,
+ 'upstream': instance.upstream,
+ 'ahead': instance.ahead,
+ 'behind': instance.behind,
+ };
+
+_$DiffFileImpl _$$DiffFileImplFromJson(Map json) =>
+ _$DiffFileImpl(
+ path: json['path'] as String,
+ oldPath: json['oldPath'] as String,
+ newPath: json['newPath'] as String,
+ status: $enumDecode(_$FileChangeStatusEnumMap, json['status']),
+ additions: (json['additions'] as num).toInt(),
+ deletions: (json['deletions'] as num).toInt(),
+ hunks: (json['hunks'] as List)
+ .map((e) => DiffHunk.fromJson(e as Map))
+ .toList(),
+ oldMode: json['oldMode'] as String?,
+ newMode: json['newMode'] as String?,
+ );
+
+Map _$$DiffFileImplToJson(_$DiffFileImpl instance) =>
+ {
+ 'path': instance.path,
+ 'oldPath': instance.oldPath,
+ 'newPath': instance.newPath,
+ 'status': _$FileChangeStatusEnumMap[instance.status]!,
+ 'additions': instance.additions,
+ 'deletions': instance.deletions,
+ 'hunks': instance.hunks,
+ 'oldMode': instance.oldMode,
+ 'newMode': instance.newMode,
+ };
+
+_$DiffHunkImpl _$$DiffHunkImplFromJson(Map json) =>
+ _$DiffHunkImpl(
+ header: json['header'] as String,
+ oldStart: (json['oldStart'] as num).toInt(),
+ oldLines: (json['oldLines'] as num).toInt(),
+ newStart: (json['newStart'] as num).toInt(),
+ newLines: (json['newLines'] as num).toInt(),
+ lines: (json['lines'] as List)
+ .map((e) => DiffLine.fromJson(e as Map))
+ .toList(),
+ );
+
+Map _$$DiffHunkImplToJson(_$DiffHunkImpl instance) =>
+ {
+ 'header': instance.header,
+ 'oldStart': instance.oldStart,
+ 'oldLines': instance.oldLines,
+ 'newStart': instance.newStart,
+ 'newLines': instance.newLines,
+ 'lines': instance.lines,
+ };
+
+_$DiffLineImpl _$$DiffLineImplFromJson(Map json) =>
+ _$DiffLineImpl(
+ type: $enumDecode(_$DiffLineTypeEnumMap, json['type']),
+ content: json['content'] as String,
+ oldLineNumber: (json['oldLineNumber'] as num?)?.toInt(),
+ newLineNumber: (json['newLineNumber'] as num?)?.toInt(),
+ );
+
+Map _$$DiffLineImplToJson(_$DiffLineImpl instance) =>
+ {
+ 'type': _$DiffLineTypeEnumMap[instance.type]!,
+ 'content': instance.content,
+ 'oldLineNumber': instance.oldLineNumber,
+ 'newLineNumber': instance.newLineNumber,
+ };
+
+const _$DiffLineTypeEnumMap = {
+ DiffLineType.context: 'context',
+ DiffLineType.added: 'added',
+ DiffLineType.removed: 'removed',
+};
diff --git a/apps/mobile/lib/core/models/hook_models.dart b/apps/mobile/lib/core/models/hook_models.dart
new file mode 100644
index 0000000..ca80534
--- /dev/null
+++ b/apps/mobile/lib/core/models/hook_models.dart
@@ -0,0 +1,29 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'hook_models.freezed.dart';
+part 'hook_models.g.dart';
+
+enum HookEventType {
+ sessionStart,
+ sessionEnd,
+ preToolUse,
+ postToolUse,
+ userPromptSubmit,
+ stop,
+ subagentStop,
+ preCompact,
+ notification,
+}
+
+@freezed
+class HookEvent with _$HookEvent {
+ const factory HookEvent({
+ required String eventType,
+ required String sessionId,
+ required DateTime timestamp,
+ required Map payload,
+ }) = _HookEvent;
+
+ factory HookEvent.fromJson(Map json) =>
+ _$HookEventFromJson(json);
+}
diff --git a/apps/mobile/lib/core/models/hook_models.freezed.dart b/apps/mobile/lib/core/models/hook_models.freezed.dart
new file mode 100644
index 0000000..73935f2
--- /dev/null
+++ b/apps/mobile/lib/core/models/hook_models.freezed.dart
@@ -0,0 +1,237 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// 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 'hook_models.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+HookEvent _$HookEventFromJson(Map json) {
+ return _HookEvent.fromJson(json);
+}
+
+/// @nodoc
+mixin _$HookEvent {
+ String get eventType => throw _privateConstructorUsedError;
+ String get sessionId => throw _privateConstructorUsedError;
+ DateTime get timestamp => throw _privateConstructorUsedError;
+ Map get payload => throw _privateConstructorUsedError;
+
+ /// Serializes this HookEvent to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+
+ /// Create a copy of HookEvent
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ $HookEventCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $HookEventCopyWith<$Res> {
+ factory $HookEventCopyWith(HookEvent value, $Res Function(HookEvent) then) =
+ _$HookEventCopyWithImpl<$Res, HookEvent>;
+ @useResult
+ $Res call(
+ {String eventType,
+ String sessionId,
+ DateTime timestamp,
+ Map payload});
+}
+
+/// @nodoc
+class _$HookEventCopyWithImpl<$Res, $Val extends HookEvent>
+ implements $HookEventCopyWith<$Res> {
+ _$HookEventCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of HookEvent
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? eventType = null,
+ Object? sessionId = null,
+ Object? timestamp = null,
+ Object? payload = null,
+ }) {
+ return _then(_value.copyWith(
+ eventType: null == eventType
+ ? _value.eventType
+ : eventType // ignore: cast_nullable_to_non_nullable
+ as String,
+ sessionId: null == sessionId
+ ? _value.sessionId
+ : sessionId // ignore: cast_nullable_to_non_nullable
+ as String,
+ timestamp: null == timestamp
+ ? _value.timestamp
+ : timestamp // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ payload: null == payload
+ ? _value.payload
+ : payload // ignore: cast_nullable_to_non_nullable
+ as Map,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$HookEventImplCopyWith<$Res>
+ implements $HookEventCopyWith<$Res> {
+ factory _$$HookEventImplCopyWith(
+ _$HookEventImpl value, $Res Function(_$HookEventImpl) then) =
+ __$$HookEventImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {String eventType,
+ String sessionId,
+ DateTime timestamp,
+ Map payload});
+}
+
+/// @nodoc
+class __$$HookEventImplCopyWithImpl<$Res>
+ extends _$HookEventCopyWithImpl<$Res, _$HookEventImpl>
+ implements _$$HookEventImplCopyWith<$Res> {
+ __$$HookEventImplCopyWithImpl(
+ _$HookEventImpl _value, $Res Function(_$HookEventImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of HookEvent
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? eventType = null,
+ Object? sessionId = null,
+ Object? timestamp = null,
+ Object? payload = null,
+ }) {
+ return _then(_$HookEventImpl(
+ eventType: null == eventType
+ ? _value.eventType
+ : eventType // ignore: cast_nullable_to_non_nullable
+ as String,
+ sessionId: null == sessionId
+ ? _value.sessionId
+ : sessionId // ignore: cast_nullable_to_non_nullable
+ as String,
+ timestamp: null == timestamp
+ ? _value.timestamp
+ : timestamp // ignore: cast_nullable_to_non_nullable
+ as DateTime,
+ payload: null == payload
+ ? _value._payload
+ : payload // ignore: cast_nullable_to_non_nullable
+ as Map,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$HookEventImpl implements _HookEvent {
+ const _$HookEventImpl(
+ {required this.eventType,
+ required this.sessionId,
+ required this.timestamp,
+ required final Map payload})
+ : _payload = payload;
+
+ factory _$HookEventImpl.fromJson(Map json) =>
+ _$$HookEventImplFromJson(json);
+
+ @override
+ final String eventType;
+ @override
+ final String sessionId;
+ @override
+ final DateTime timestamp;
+ final Map _payload;
+ @override
+ Map get payload {
+ if (_payload is EqualUnmodifiableMapView) return _payload;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableMapView(_payload);
+ }
+
+ @override
+ String toString() {
+ return 'HookEvent(eventType: $eventType, sessionId: $sessionId, timestamp: $timestamp, payload: $payload)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$HookEventImpl &&
+ (identical(other.eventType, eventType) ||
+ other.eventType == eventType) &&
+ (identical(other.sessionId, sessionId) ||
+ other.sessionId == sessionId) &&
+ (identical(other.timestamp, timestamp) ||
+ other.timestamp == timestamp) &&
+ const DeepCollectionEquality().equals(other._payload, _payload));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, eventType, sessionId, timestamp,
+ const DeepCollectionEquality().hash(_payload));
+
+ /// Create a copy of HookEvent
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$HookEventImplCopyWith<_$HookEventImpl> get copyWith =>
+ __$$HookEventImplCopyWithImpl<_$HookEventImpl>(this, _$identity);
+
+ @override
+ Map toJson() {
+ return _$$HookEventImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class _HookEvent implements HookEvent {
+ const factory _HookEvent(
+ {required final String eventType,
+ required final String sessionId,
+ required final DateTime timestamp,
+ required final Map payload}) = _$HookEventImpl;
+
+ factory _HookEvent.fromJson(Map json) =
+ _$HookEventImpl.fromJson;
+
+ @override
+ String get eventType;
+ @override
+ String get sessionId;
+ @override
+ DateTime get timestamp;
+ @override
+ Map get payload;
+
+ /// Create a copy of HookEvent
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$HookEventImplCopyWith<_$HookEventImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/apps/mobile/lib/core/models/hook_models.g.dart b/apps/mobile/lib/core/models/hook_models.g.dart
new file mode 100644
index 0000000..142077b
--- /dev/null
+++ b/apps/mobile/lib/core/models/hook_models.g.dart
@@ -0,0 +1,23 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'hook_models.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$HookEventImpl _$$HookEventImplFromJson(Map json) =>
+ _$HookEventImpl(
+ eventType: json['eventType'] as String,
+ sessionId: json['sessionId'] as String,
+ timestamp: DateTime.parse(json['timestamp'] as String),
+ payload: json['payload'] as Map,
+ );
+
+Map _$$HookEventImplToJson(_$HookEventImpl instance) =>
+ {
+ 'eventType': instance.eventType,
+ 'sessionId': instance.sessionId,
+ 'timestamp': instance.timestamp.toIso8601String(),
+ 'payload': instance.payload,
+ };
diff --git a/apps/mobile/lib/core/models/message_models.dart b/apps/mobile/lib/core/models/message_models.dart
new file mode 100644
index 0000000..45c9822
--- /dev/null
+++ b/apps/mobile/lib/core/models/message_models.dart
@@ -0,0 +1,91 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'message_models.freezed.dart';
+part 'message_models.g.dart';
+
+enum MessageRole { user, agent, system }
+
+enum MessageType { text, toolCall, toolResult, system }
+
+@freezed
+class MessagePart with _$MessagePart {
+ const factory MessagePart.text({
+ required String content,
+ }) = TextPart;
+
+ const factory MessagePart.toolUse({
+ required String tool,
+ required Map params,
+ String? id,
+ }) = ToolUsePart;
+
+ const factory MessagePart.toolResult({
+ required String toolCallId,
+ required ToolResult result,
+ }) = ToolResultPart;
+
+ const factory MessagePart.thinking({
+ required String content,
+ }) = ThinkingPart;
+
+ factory MessagePart.fromJson(Map json) =>
+ _$MessagePartFromJson(json);
+}
+
+@freezed
+class ToolResult with _$ToolResult {
+ const factory ToolResult({
+ required bool success,
+ required String content,
+ Map? metadata,
+ String? error,
+ int? durationMs,
+ }) = _ToolResult;
+
+ factory ToolResult.fromJson(Map json) =>
+ _$ToolResultFromJson(json);
+}
+
+enum RiskLevel { low, medium, high, critical }
+
+enum ApprovalDecision { pending, approved, rejected, modified }
+
+@freezed
+class ToolCall with _$ToolCall {
+ const factory ToolCall({
+ required String id,
+ required String sessionId,
+ required String tool,
+ required Map params,
+ String? description,
+ String? reasoning,
+ @Default(RiskLevel.low) RiskLevel riskLevel,
+ @Default(ApprovalDecision.pending) ApprovalDecision decision,
+ String? modifications,
+ Map? result,
+ required DateTime createdAt,
+ DateTime? decidedAt,
+ }) = _ToolCall;
+
+ factory ToolCall.fromJson(Map json) =>
+ _$ToolCallFromJson(json);
+}
+
+@freezed
+class Message with _$Message {
+ const factory Message({
+ required String id,
+ required String sessionId,
+ required MessageRole role,
+ required String content,
+ required MessageType type,
+ required List parts,
+ Map? metadata,
+ required DateTime createdAt,
+ DateTime? updatedAt,
+ @Default(true) bool synced,
+ }) = _Message;
+
+ factory Message.fromJson(Map json) =>
+ _$MessageFromJson(json);
+}
diff --git a/apps/mobile/lib/core/models/message_models.freezed.dart b/apps/mobile/lib/core/models/message_models.freezed.dart
new file mode 100644
index 0000000..771541f
--- /dev/null
+++ b/apps/mobile/lib/core/models/message_models.freezed.dart
@@ -0,0 +1,1885 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// 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 'message_models.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+ 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+MessagePart _$MessagePartFromJson(Map json) {
+ switch (json['runtimeType']) {
+ case 'text':
+ return TextPart.fromJson(json);
+ case 'toolUse':
+ return ToolUsePart.fromJson(json);
+ case 'toolResult':
+ return ToolResultPart.fromJson(json);
+ case 'thinking':
+ return ThinkingPart.fromJson(json);
+
+ default:
+ throw CheckedFromJsonException(json, 'runtimeType', 'MessagePart',
+ 'Invalid union type "${json['runtimeType']}"!');
+ }
+}
+
+/// @nodoc
+mixin _$MessagePart {
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function(String content) text,
+ required TResult Function(
+ String tool, Map params, String? id)
+ toolUse,
+ required TResult Function(String toolCallId, ToolResult result) toolResult,
+ required TResult Function(String content) thinking,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function(String content)? text,
+ TResult? Function(String tool, Map params, String? id)?
+ toolUse,
+ TResult? Function(String toolCallId, ToolResult result)? toolResult,
+ TResult? Function(String content)? thinking,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function(String content)? text,
+ TResult Function(String tool, Map params, String? id)?
+ toolUse,
+ TResult Function(String toolCallId, ToolResult result)? toolResult,
+ TResult Function(String content)? thinking,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(TextPart value) text,
+ required TResult Function(ToolUsePart value) toolUse,
+ required TResult Function(ToolResultPart value) toolResult,
+ required TResult Function(ThinkingPart value) thinking,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(TextPart value)? text,
+ TResult? Function(ToolUsePart value)? toolUse,
+ TResult? Function(ToolResultPart value)? toolResult,
+ TResult? Function(ThinkingPart value)? thinking,
+ }) =>
+ throw _privateConstructorUsedError;
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(TextPart value)? text,
+ TResult Function(ToolUsePart value)? toolUse,
+ TResult Function(ToolResultPart value)? toolResult,
+ TResult Function(ThinkingPart value)? thinking,
+ required TResult orElse(),
+ }) =>
+ throw _privateConstructorUsedError;
+
+ /// Serializes this MessagePart to a JSON map.
+ Map toJson() => throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $MessagePartCopyWith<$Res> {
+ factory $MessagePartCopyWith(
+ MessagePart value, $Res Function(MessagePart) then) =
+ _$MessagePartCopyWithImpl<$Res, MessagePart>;
+}
+
+/// @nodoc
+class _$MessagePartCopyWithImpl<$Res, $Val extends MessagePart>
+ implements $MessagePartCopyWith<$Res> {
+ _$MessagePartCopyWithImpl(this._value, this._then);
+
+ // ignore: unused_field
+ final $Val _value;
+ // ignore: unused_field
+ final $Res Function($Val) _then;
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+}
+
+/// @nodoc
+abstract class _$$TextPartImplCopyWith<$Res> {
+ factory _$$TextPartImplCopyWith(
+ _$TextPartImpl value, $Res Function(_$TextPartImpl) then) =
+ __$$TextPartImplCopyWithImpl<$Res>;
+ @useResult
+ $Res call({String content});
+}
+
+/// @nodoc
+class __$$TextPartImplCopyWithImpl<$Res>
+ extends _$MessagePartCopyWithImpl<$Res, _$TextPartImpl>
+ implements _$$TextPartImplCopyWith<$Res> {
+ __$$TextPartImplCopyWithImpl(
+ _$TextPartImpl _value, $Res Function(_$TextPartImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? content = null,
+ }) {
+ return _then(_$TextPartImpl(
+ content: null == content
+ ? _value.content
+ : content // ignore: cast_nullable_to_non_nullable
+ as String,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$TextPartImpl implements TextPart {
+ const _$TextPartImpl({required this.content, final String? $type})
+ : $type = $type ?? 'text';
+
+ factory _$TextPartImpl.fromJson(Map json) =>
+ _$$TextPartImplFromJson(json);
+
+ @override
+ final String content;
+
+ @JsonKey(name: 'runtimeType')
+ final String $type;
+
+ @override
+ String toString() {
+ return 'MessagePart.text(content: $content)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$TextPartImpl &&
+ (identical(other.content, content) || other.content == content));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, content);
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$TextPartImplCopyWith<_$TextPartImpl> get copyWith =>
+ __$$TextPartImplCopyWithImpl<_$TextPartImpl>(this, _$identity);
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function(String content) text,
+ required TResult Function(
+ String tool, Map params, String? id)
+ toolUse,
+ required TResult Function(String toolCallId, ToolResult result) toolResult,
+ required TResult Function(String content) thinking,
+ }) {
+ return text(content);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function(String content)? text,
+ TResult? Function(String tool, Map params, String? id)?
+ toolUse,
+ TResult? Function(String toolCallId, ToolResult result)? toolResult,
+ TResult? Function(String content)? thinking,
+ }) {
+ return text?.call(content);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function(String content)? text,
+ TResult Function(String tool, Map params, String? id)?
+ toolUse,
+ TResult Function(String toolCallId, ToolResult result)? toolResult,
+ TResult Function(String content)? thinking,
+ required TResult orElse(),
+ }) {
+ if (text != null) {
+ return text(content);
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(TextPart value) text,
+ required TResult Function(ToolUsePart value) toolUse,
+ required TResult Function(ToolResultPart value) toolResult,
+ required TResult Function(ThinkingPart value) thinking,
+ }) {
+ return text(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(TextPart value)? text,
+ TResult? Function(ToolUsePart value)? toolUse,
+ TResult? Function(ToolResultPart value)? toolResult,
+ TResult? Function(ThinkingPart value)? thinking,
+ }) {
+ return text?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(TextPart value)? text,
+ TResult Function(ToolUsePart value)? toolUse,
+ TResult Function(ToolResultPart value)? toolResult,
+ TResult Function(ThinkingPart value)? thinking,
+ required TResult orElse(),
+ }) {
+ if (text != null) {
+ return text(this);
+ }
+ return orElse();
+ }
+
+ @override
+ Map toJson() {
+ return _$$TextPartImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class TextPart implements MessagePart {
+ const factory TextPart({required final String content}) = _$TextPartImpl;
+
+ factory TextPart.fromJson(Map json) =
+ _$TextPartImpl.fromJson;
+
+ String get content;
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$TextPartImplCopyWith<_$TextPartImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class _$$ToolUsePartImplCopyWith<$Res> {
+ factory _$$ToolUsePartImplCopyWith(
+ _$ToolUsePartImpl value, $Res Function(_$ToolUsePartImpl) then) =
+ __$$ToolUsePartImplCopyWithImpl<$Res>;
+ @useResult
+ $Res call({String tool, Map params, String? id});
+}
+
+/// @nodoc
+class __$$ToolUsePartImplCopyWithImpl<$Res>
+ extends _$MessagePartCopyWithImpl<$Res, _$ToolUsePartImpl>
+ implements _$$ToolUsePartImplCopyWith<$Res> {
+ __$$ToolUsePartImplCopyWithImpl(
+ _$ToolUsePartImpl _value, $Res Function(_$ToolUsePartImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? tool = null,
+ Object? params = null,
+ Object? id = freezed,
+ }) {
+ return _then(_$ToolUsePartImpl(
+ tool: null == tool
+ ? _value.tool
+ : tool // ignore: cast_nullable_to_non_nullable
+ as String,
+ params: null == params
+ ? _value._params
+ : params // ignore: cast_nullable_to_non_nullable
+ as Map,
+ id: freezed == id
+ ? _value.id
+ : id // ignore: cast_nullable_to_non_nullable
+ as String?,
+ ));
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$ToolUsePartImpl implements ToolUsePart {
+ const _$ToolUsePartImpl(
+ {required this.tool,
+ required final Map params,
+ this.id,
+ final String? $type})
+ : _params = params,
+ $type = $type ?? 'toolUse';
+
+ factory _$ToolUsePartImpl.fromJson(Map json) =>
+ _$$ToolUsePartImplFromJson(json);
+
+ @override
+ final String tool;
+ final Map _params;
+ @override
+ Map get params {
+ if (_params is EqualUnmodifiableMapView) return _params;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableMapView(_params);
+ }
+
+ @override
+ final String? id;
+
+ @JsonKey(name: 'runtimeType')
+ final String $type;
+
+ @override
+ String toString() {
+ return 'MessagePart.toolUse(tool: $tool, params: $params, id: $id)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$ToolUsePartImpl &&
+ (identical(other.tool, tool) || other.tool == tool) &&
+ const DeepCollectionEquality().equals(other._params, _params) &&
+ (identical(other.id, id) || other.id == id));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(
+ runtimeType, tool, const DeepCollectionEquality().hash(_params), id);
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$ToolUsePartImplCopyWith<_$ToolUsePartImpl> get copyWith =>
+ __$$ToolUsePartImplCopyWithImpl<_$ToolUsePartImpl>(this, _$identity);
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function(String content) text,
+ required TResult Function(
+ String tool, Map params, String? id)
+ toolUse,
+ required TResult Function(String toolCallId, ToolResult result) toolResult,
+ required TResult Function(String content) thinking,
+ }) {
+ return toolUse(tool, params, id);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function(String content)? text,
+ TResult? Function(String tool, Map params, String? id)?
+ toolUse,
+ TResult? Function(String toolCallId, ToolResult result)? toolResult,
+ TResult? Function(String content)? thinking,
+ }) {
+ return toolUse?.call(tool, params, id);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function(String content)? text,
+ TResult Function(String tool, Map params, String? id)?
+ toolUse,
+ TResult Function(String toolCallId, ToolResult result)? toolResult,
+ TResult Function(String content)? thinking,
+ required TResult orElse(),
+ }) {
+ if (toolUse != null) {
+ return toolUse(tool, params, id);
+ }
+ return orElse();
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult map({
+ required TResult Function(TextPart value) text,
+ required TResult Function(ToolUsePart value) toolUse,
+ required TResult Function(ToolResultPart value) toolResult,
+ required TResult Function(ThinkingPart value) thinking,
+ }) {
+ return toolUse(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? mapOrNull({
+ TResult? Function(TextPart value)? text,
+ TResult? Function(ToolUsePart value)? toolUse,
+ TResult? Function(ToolResultPart value)? toolResult,
+ TResult? Function(ThinkingPart value)? thinking,
+ }) {
+ return toolUse?.call(this);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeMap({
+ TResult Function(TextPart value)? text,
+ TResult Function(ToolUsePart value)? toolUse,
+ TResult Function(ToolResultPart value)? toolResult,
+ TResult Function(ThinkingPart value)? thinking,
+ required TResult orElse(),
+ }) {
+ if (toolUse != null) {
+ return toolUse(this);
+ }
+ return orElse();
+ }
+
+ @override
+ Map toJson() {
+ return _$$ToolUsePartImplToJson(
+ this,
+ );
+ }
+}
+
+abstract class ToolUsePart implements MessagePart {
+ const factory ToolUsePart(
+ {required final String tool,
+ required final Map params,
+ final String? id}) = _$ToolUsePartImpl;
+
+ factory ToolUsePart.fromJson(Map json) =
+ _$ToolUsePartImpl.fromJson;
+
+ String get tool;
+ Map get params;
+ String? get id;
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ _$$ToolUsePartImplCopyWith<_$ToolUsePartImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class _$$ToolResultPartImplCopyWith<$Res> {
+ factory _$$ToolResultPartImplCopyWith(_$ToolResultPartImpl value,
+ $Res Function(_$ToolResultPartImpl) then) =
+ __$$ToolResultPartImplCopyWithImpl<$Res>;
+ @useResult
+ $Res call({String toolCallId, ToolResult result});
+
+ $ToolResultCopyWith<$Res> get result;
+}
+
+/// @nodoc
+class __$$ToolResultPartImplCopyWithImpl<$Res>
+ extends _$MessagePartCopyWithImpl<$Res, _$ToolResultPartImpl>
+ implements _$$ToolResultPartImplCopyWith<$Res> {
+ __$$ToolResultPartImplCopyWithImpl(
+ _$ToolResultPartImpl _value, $Res Function(_$ToolResultPartImpl) _then)
+ : super(_value, _then);
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? toolCallId = null,
+ Object? result = null,
+ }) {
+ return _then(_$ToolResultPartImpl(
+ toolCallId: null == toolCallId
+ ? _value.toolCallId
+ : toolCallId // ignore: cast_nullable_to_non_nullable
+ as String,
+ result: null == result
+ ? _value.result
+ : result // ignore: cast_nullable_to_non_nullable
+ as ToolResult,
+ ));
+ }
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @override
+ @pragma('vm:prefer-inline')
+ $ToolResultCopyWith<$Res> get result {
+ return $ToolResultCopyWith<$Res>(_value.result, (value) {
+ return _then(_value.copyWith(result: value));
+ });
+ }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$ToolResultPartImpl implements ToolResultPart {
+ const _$ToolResultPartImpl(
+ {required this.toolCallId, required this.result, final String? $type})
+ : $type = $type ?? 'toolResult';
+
+ factory _$ToolResultPartImpl.fromJson(Map json) =>
+ _$$ToolResultPartImplFromJson(json);
+
+ @override
+ final String toolCallId;
+ @override
+ final ToolResult result;
+
+ @JsonKey(name: 'runtimeType')
+ final String $type;
+
+ @override
+ String toString() {
+ return 'MessagePart.toolResult(toolCallId: $toolCallId, result: $result)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$ToolResultPartImpl &&
+ (identical(other.toolCallId, toolCallId) ||
+ other.toolCallId == toolCallId) &&
+ (identical(other.result, result) || other.result == result));
+ }
+
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ int get hashCode => Object.hash(runtimeType, toolCallId, result);
+
+ /// Create a copy of MessagePart
+ /// with the given fields replaced by the non-null parameter values.
+ @JsonKey(includeFromJson: false, includeToJson: false)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$ToolResultPartImplCopyWith<_$ToolResultPartImpl> get copyWith =>
+ __$$ToolResultPartImplCopyWithImpl<_$ToolResultPartImpl>(
+ this, _$identity);
+
+ @override
+ @optionalTypeArgs
+ TResult when({
+ required TResult Function(String content) text,
+ required TResult Function(
+ String tool, Map params, String? id)
+ toolUse,
+ required TResult Function(String toolCallId, ToolResult result) toolResult,
+ required TResult Function(String content) thinking,
+ }) {
+ return toolResult(toolCallId, result);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult? whenOrNull({
+ TResult? Function(String content)? text,
+ TResult? Function(String tool, Map params, String? id)?
+ toolUse,
+ TResult? Function(String toolCallId, ToolResult result)? toolResult,
+ TResult? Function(String content)? thinking,
+ }) {
+ return toolResult?.call(toolCallId, result);
+ }
+
+ @override
+ @optionalTypeArgs
+ TResult maybeWhen({
+ TResult Function(String content)? text,
+ TResult Function(String tool, Map