Skip to content
This repository was archived by the owner on Dec 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ migrate_working_dir/
/build/
/coverage/

# Flutter ephemeral directories (auto-generated)
**/linux/flutter/ephemeral/
**/macos/Flutter/ephemeral/
**/windows/flutter/ephemeral/

# Symbolication related
app.*.symbols

Expand Down
114 changes: 111 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,22 @@ All services use **Riverpod dependency injection** (no singletons):
- Auto-initialized via `storageServiceProvider`

3. **WhisperService** (`lib/services/whisper_service.dart`)
- OpenAI Whisper API integration for transcription
- OpenAI Whisper API integration for cloud transcription
- API key management via StorageService
- Accessed via `whisperServiceProvider`

4. **RecordingRepository** (`lib/repositories/recording_repository.dart`)
4. **WhisperLocalService** (`lib/services/whisper_local_service.dart`)
- Local on-device transcription using Whisper models
- Offline, private, and free transcription
- Progress tracking with callbacks
- Accessed via `whisperLocalServiceProvider`

5. **WhisperModelManager** (`lib/services/whisper_model_manager.dart`)
- Manages Whisper model downloads and lifecycle
- Tracks download progress and storage usage
- Accessed via `whisperModelManagerProvider`

6. **RecordingRepository** (`lib/repositories/recording_repository.dart`)
- Repository pattern for data access
- Clean CRUD API
- Accessed via `recordingRepositoryProvider`
Expand Down Expand Up @@ -123,6 +134,7 @@ All services use **Riverpod dependency injection** (no singletons):
- `path_provider ^2.0.0` - File system access
- `shared_preferences ^2.0.0` - Settings persistence
- `http ^1.2.0` - Whisper API calls
- `whisper_ggml ^1.7.0` - Local Whisper transcription
- `google_fonts ^6.1.0` - Typography

**Dev packages:**
Expand All @@ -144,16 +156,112 @@ Run tests:
flutter test
```

## Transcription

The app supports **two transcription modes**:

### 1. OpenAI API (Cloud-based)

- Uses OpenAI's Whisper API
- Requires internet connection and API key
- Cost: ~$0.006 per minute
- Best quality and accuracy
- Configure API key in Settings

### 2. Local (On-device)

- Uses local Whisper models via `whisper_ggml`
- Completely offline and private
- Free (no API costs)
- Download models in Settings
- Available models:
- **tiny** (75 MB) - Fast, good for real-time
- **base** (142 MB) - Balanced speed and accuracy (recommended)
- **small** (466 MB) - Better accuracy, slower
- **medium** (1.5 GB) - High accuracy, much slower
- **large** (2.9 GB) - Best quality, very slow

**Features**:

- Transcription mode selector (API vs Local)
- Auto-transcribe toggle (automatic transcription after recording)
- Progress tracking for local transcription
- Model download management with progress indicators
- Storage usage tracking

## Important Notes

- App uses **Riverpod** - access services via `ref.read(serviceProvider)`
- Recordings stored as `.m4a` (audio) + `.md` (metadata)
- Sample recordings created on first launch
- Transcription requires OpenAI API key (configured in Settings)
- Transcription works offline with local models or via OpenAI API
- File-based sync for cross-device support
- Global error boundaries configured in main.dart
- Production-ready with comprehensive validation

## Omi Device Integration

The app supports integration with Omi wearable devices for voice recording via Bluetooth Low Energy (BLE).

### Firmware

**Location**: `firmware/`

The firmware is built on Zephyr RTOS for nRF52840 chips (Seeed XIAO nRF52840 Sense). Key features:

- Smart button controls (single/double/triple tap)
- Audio streaming over BLE (PCM8/16, Opus, ΞΌLaw codecs)
- LED status indicators (red=recording, blue=connected, green=charging)
- Over-the-air (OTA) firmware updates

**Current Version**: 2.0.12

**Building Firmware**:

```bash
cd firmware
./scripts/build-docker.sh # Build only
./scripts/build-and-integrate.sh # Build + copy to assets
```

See `firmware/README.md` for detailed firmware development guide.

### BLE Integration

**Services** (`lib/services/omi/`):

- `omi_bluetooth_service.dart` - Device scanning and connection
- `omi_connection.dart` - BLE GATT communication
- `omi_capture_service.dart` - Recording orchestration

**Providers** (`lib/providers/omi_providers.dart`):

- `omiBluetoothServiceProvider` - BLE service
- `omiCaptureServiceProvider` - Capture service
- `connectedOmiDeviceProvider` - Device state
- `lastPairedDeviceProvider` - Persistent pairing

**Button Tap Behavior**:

- Single tap to stop: Standard recording
- Double tap to stop: AI Query (future feature)
- Triple tap to stop: Knowledge Capture (future feature)

**Recording Flow**:

1. Device button pressed β†’ BLE event to app
2. App starts capture service β†’ Listens for audio stream
3. Device streams audio packets β†’ App assembles into WAV file
4. Device button released (with tap count) β†’ App stops recording
5. Recording saved with source=omiDevice, deviceId, buttonTapCount

### Platform Support

- **iOS/Android**: Full BLE support
- **macOS**: Gracefully degrades (shows "not supported" message)

Platform checks via `PlatformUtils.shouldShowOmiFeatures`

## Code Style

- Use `debugPrint()` not `print()`
Expand Down
11 changes: 8 additions & 3 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ if (keystorePropertiesFile.exists()) {

android {
namespace = "com.mycompany.CounterApp"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileSdk = 36
ndkVersion = "29.0.13113456"

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
coreLibraryDesugaringEnabled = true
}

kotlinOptions {
Expand All @@ -40,7 +41,7 @@ android {
applicationId = "com.mycompany.CounterApp"
// 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
minSdkVersion = flutter.minSdkVersion // Required by opus_flutter_android and other native dependencies
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
Expand All @@ -55,6 +56,10 @@ android {
}
}

dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.4'
}

flutter {
source = "../.."
}
9 changes: 9 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<!-- Bluetooth permissions for Omi device -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<!-- Notifications for background recording status -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="EchoForge"
android:name="${applicationName}"
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ subprojects {
if (project.plugins.hasPlugin("com.android.application") ||
project.plugins.hasPlugin("com.android.library")) {
project.android {
compileSdkVersion 34
compileSdkVersion 36
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
Expand Down
1 change: 1 addition & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
android.ndk.suppressMinSdkVersionError=21
2 changes: 1 addition & 1 deletion android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pluginManagement {

plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.7.3" apply false
id "com.android.application" version "8.9.1" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
}

Expand Down
72 changes: 72 additions & 0 deletions assets/firmware/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Firmware Assets

This directory contains compiled Omi device firmware for over-the-air (OTA) updates.

## Current Firmware

Place compiled firmware binaries here with version-specific naming:
- `devkit-v2-firmware-VERSION.zip` - Version-specific firmware
- `devkit-v2-firmware-latest.zip` - Symlink to latest version (optional)
- `BUILD_INFO.txt` - Build metadata (auto-generated)

## Building Firmware

To build firmware and copy it here automatically:

```bash
cd firmware
./scripts/build-and-integrate.sh
```

This will:
1. Compile firmware using Docker
2. Copy `zephyr.zip` to this directory with version number
3. Create symlink for easy reference
4. Generate build information

## Manual Integration

If you've built firmware separately:

```bash
# Copy manually
cp firmware/build/docker_build/zephyr.zip assets/firmware/devkit-v2-firmware-X.Y.Z.zip

# Update pubspec.yaml if needed
flutter pub get
```

## OTA Update Flow

1. App connects to Omi device via BLE
2. App reads firmware version from device
3. If newer firmware available in assets, prompts user
4. User confirms update
5. App initiates Nordic DFU protocol
6. Device updates and reboots

## Version Management

Firmware version is defined in:
```
firmware/devkit/prj_xiao_ble_sense_devkitv2-adafruit.conf
CONFIG_BT_DIS_FW_REV_STR="X.Y.Z"
```

Asset filename must match this version for proper OTA detection.

## File Size

Typical firmware size: ~500KB - 1MB (compressed)

## Adding to Flutter Assets

Ensure `pubspec.yaml` includes:

```yaml
flutter:
assets:
- assets/firmware/
```

Then run `flutter pub get` to register new assets.
Loading