From 33fb3974d722146ad4b39efc3a5f628c21d86793 Mon Sep 17 00:00:00 2001 From: ultraelectronica Date: Sun, 28 Jun 2026 23:17:01 +0800 Subject: [PATCH] Trim changelog, condense README to core details --- CHANGELOG.md | 143 +++++++-------- README.md | 353 +++++++++---------------------------- assets/privacy_policy.md | 74 ++++---- docs/architecture_media.md | 161 ++++++----------- docs/flick_integration.md | 164 ++++------------- docs/unlock_autofill.md | 62 +++---- 6 files changed, 302 insertions(+), 655 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5cdee9..c1ead36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,49 +1,6 @@ # Changelog -All notable changes to Latch are documented in this file. - -## 0.14.4-beta.5 - -### Security -- **Argon2id key wrapping (H1)** — the vault master key is now wrapped by an Argon2id-derived key-wrapping key from the user credential; PIN/password actually protects the vault. Transparent migration on next unlock. -- **KDF iterations 100k → 600k**; selectable options `[100k, 300k, 600k, 1M]` -- **AES-256-GCM is the default cipher** for new media; silent CBC→CTR fallback removed -- **Constant-time credential comparison** (`secure_compare.dart`) in AuthService and DecoyService verify paths; legacy SHA-256 branch padded with dummy PBKDF2 work -- **Decoy PIN minimum 4 → 6 digits** -- **Password-strength enforcement** — `minPasswordLength = 8` + `validatePasswordStrength()` centralized in AuthService -- **Plaintext temp wiped** — re-encryption/rotation intermediates written to app-private temp and `secureDelete`d -- **iOS Keychain accessibility** set to `unlocked_this_device` (Android-only app, low-impact but correct) -- Raw `$e` removed from all crypto/UI exception strings; detail kept in `debugPrint` - -### Architecture -- **Crypto module split (3.1)** — `encryption_service.dart` split into pure, tested modules under `lib/crypto/`: `AesGcmCipher`, `AesCtrCipher`, `KeyDerivation`, `HeaderCodec`, `KeyWrap`. `EncryptionService` is now a behavior-preserving facade. +33 round-trip/auth tests. -- **Vault cache-NPE fix (3.2 partial)** — `refresh()` now swaps caches atomically instead of nulling first, closing the crash vector where an in-flight mutation could NPE on a nulled cache. Full repository split deferred. - -### UI & Cleanup -- **Password vault integration** — open/create password entries from the gallery screen -- **Search and sort** moved into the overflow menu -- **Centralized toasts** (`ToastUtils` via global `navigatorKey`); `fluttertoast` dependency dropped -- **Centralized paths** (`PathUtils.getDownloadsDirectory()` + `androidSourceRoots`) -- **FileTypeColors** map added to `app_colors.dart` -- **Dead code purge** — deleted `OptimizedScrollView`, `OptimizedGridView`, `OptimizedThumbnail`, `PerformanceOverlayWidget`, `CompactPermissionWarning`, `showOperationProgressSheet`, `compression_options_dialog`, dead `locker_logo_512.png` asset, dead `_importFromDocuments` method -- **Vestigial Firebase removed** — `google-services` plugin, `firebase-bom`, and `google-services.json` deleted (no Dart Firebase packages consumed) -- **`.metadata` cleanup** — removed ios/linux/macos/web/windows platform entries - -### Platform & Tooling -- **CI workflow** (`github/workflows/ci.yml`) — pub get / analyze / test on push -- **ProGuard rules** for Flutter, crypto, and autofill service -- **AndroidManifest** — `allowBackup="false"` + `dataExtractionRules` + `networkSecurityConfig` -- **Release builds** — `minifyEnabled` / `shrinkResources` / `proguardFiles` -- `permission_service.dart` hardcoded SDK 33 → `device_info_plus` -- Dropped unused deps `encrypt`, `cupertino_icons`; `fluttertoast` removed - -### Fixes -- `media_viewer_screen.dart` delete-from-viewer no longer mutates the caller's file list (operates on a local copy) -- `gallery_vault_screen.dart` `mounted` guards on 8 `setState`-after-await sites -- `note_editor_screen.dart` folder-picker tautology bug fixed -- `TextEditingController`s now disposed in dialogs/sheets (10 sites) -- Sub-48px tap targets replaced with `IconButton` -- `main.dart` update-check `StreamSubscription` stored + cancelled in `dispose()` +Notable changes to Latch. ## 0.15.0-beta.1 @@ -51,9 +8,9 @@ All notable changes to Latch are documented in this file. - **Password vault** — store, edit, and organize credentials inside the encrypted vault - **Password editor** with generator for strong random passwords - **Password list** with search, domain filtering, and multi-select -- **Android Autofill service** — seamlessly fill credentials in other apps and browsers +- **Android Autofill service** — fill credentials in other apps and browsers - Autofill credential toggle with security warning dialog -- **Autofill support** on PIN and password unlock inputs +- Autofill support on PIN and password unlock inputs - **Clipboard auto-clear** after pasting passwords - Password index included in vault backups @@ -69,35 +26,74 @@ All notable changes to Latch are documented in this file. - Default encryption algorithm set to **AES-256-GCM** ### Update System Rework -- **GitHub-based updates** via `url_launcher` (supports non-Play Store installs) +- **GitHub-based updates** via `url_launcher` (non-Play Store installs) - Install source detection — Play Store vs sideload vs GitHub - Pre-release version ordering with `pub_semver` ### Testing & Infrastructure - **CI workflow** for versionF branch -- Comprehensive unit tests for AES-256-GCM, CTR, crypto hygiene, password strength -- PBKDF2 isolate test suite -- Argon2id KWK derivation and AES-256-GCM key wrapping tests +- Unit tests: AES-256-GCM, CTR, crypto hygiene, password strength, PBKDF2 isolates, Argon2id KWK + key wrapping ### UI Polish - Tooltips on song player, media viewer, and password editor buttons -- SnackBar margin, shape, and elevation styling +- SnackBar margin/shape/elevation styling - Search and sort actions moved to overflow menu -- GestureDetector replaced with IconButton for better accessibility +- GestureDetector replaced with IconButton (accessibility) - `forceReload` parameter on vault cache loaders ### Security Hardening - App backup disabled - Network security config for cleartext traffic policy -- ProGuard minification enabled for release builds -- ProGuard rules for Flutter, crypto, and autofill service +- ProGuard minification on release builds; rules for Flutter, crypto, and autofill service ### Cleanup -- Removed Firebase dependencies from Android Gradle config -- Removed deprecated compression services and widgets -- Removed redundant file existence checks across the codebase -- Removed unused UI components, imports, and platform stubs -- Replaced `cupertino_icons` and `encrypt` with `device_info_plus` +- Firebase dependencies removed from Android Gradle config +- Deprecated compression services and widgets removed +- Redundant file-existence checks removed +- Unused UI components, imports, and platform stubs removed +- `cupertino_icons` and `encrypt` replaced with `device_info_plus` + +--- + +## 0.14.4-beta.5 + +### Security +- **Constant-time credential comparison** (`secure_compare.dart`) in AuthService and DecoyService verify paths; legacy SHA-256 branch padded with dummy PBKDF2 work +- **Decoy PIN minimum 4 → 6 digits** +- **Password-strength enforcement** — `minPasswordLength = 8` + `validatePasswordStrength()` centralized in AuthService +- **Plaintext temp wiped** — re-encryption/rotation intermediates written to app-private temp and `secureDelete`d +- **iOS Keychain accessibility** set to `unlocked_this_device` (Android-only app, low-impact but correct) +- Raw `$e` removed from all crypto/UI exception strings; detail kept in `debugPrint` + +### Architecture +- **Crypto module split** — `encryption_service.dart` split into pure, tested modules under `lib/crypto/`: `AesGcmCipher`, `AesCtrCipher`, `KeyDerivation`, `HeaderCodec`, `KeyWrap`. `EncryptionService` is now a behavior-preserving facade. +33 round-trip/auth tests. +- **Vault cache-NPE fix** — `refresh()` swaps caches atomically instead of nulling first, closing the crash vector where an in-flight mutation could NPE on a nulled cache. Full repository split deferred. + +### UI & Cleanup +- **Password vault integration** — open/create password entries from the gallery screen +- **Search and sort** moved into the overflow menu +- **Centralized toasts** (`ToastUtils` via global `navigatorKey`); `fluttertoast` dropped +- **Centralized paths** (`PathUtils.getDownloadsDirectory()` + `androidSourceRoots`) +- **FileTypeColors** map added to `app_colors.dart` +- **Dead code purge** — deleted `OptimizedScrollView`, `OptimizedGridView`, `OptimizedThumbnail`, `PerformanceOverlayWidget`, `CompactPermissionWarning`, `showOperationProgressSheet`, `compression_options_dialog`, dead `locker_logo_512.png`, dead `_importFromDocuments` +- **Vestigial Firebase removed** — `google-services` plugin, `firebase-bom`, `google-services.json` (no Dart Firebase consumed) +- **`.metadata` cleanup** — removed ios/linux/macos/web/windows platform entries + +### Platform & Tooling +- **CI workflow** (`github/workflows/ci.yml`) — pub get / analyze / test on push +- **ProGuard rules** for Flutter, crypto, and autofill service +- **AndroidManifest** — `allowBackup="false"` + `dataExtractionRules` + `networkSecurityConfig` +- **Release builds** — `minifyEnabled` / `shrinkResources` / `proguardFiles` +- `permission_service.dart` hardcoded SDK 33 → `device_info_plus` +- Dropped unused deps `encrypt`, `cupertino_icons`; `fluttertoast` removed + +### Fixes +- `media_viewer_screen.dart` delete-from-viewer no longer mutates the caller's file list (operates on a local copy) +- `gallery_vault_screen.dart` `mounted` guards on 8 `setState`-after-await sites +- `note_editor_screen.dart` folder-picker tautology bug fixed +- `TextEditingController`s now disposed in dialogs/sheets (10 sites) +- Sub-48px tap targets replaced with `IconButton` +- `main.dart` update-check `StreamSubscription` stored + cancelled in `dispose()` --- @@ -124,8 +120,7 @@ All notable changes to Latch are documented in this file. - **Resizable sidebar** in vault explorer - Checkmark icons on selected encryption algorithm chips - Dynamic accent color for slider thumb and track -- Redesigned password setup and change security screens -- Removed gradient backgrounds from security screens +- Redesigned password setup and change security screens; gradients removed ### Architecture - **Centralized `FileOpenService`** — all vault file opening routed through a single service @@ -143,18 +138,18 @@ All notable changes to Latch are documented in this file. ## 0.14.3-beta.4 ### Always Up-to-Date Content -- **"What's New"** highlights and **full changelog** now fetch from the GitHub repo on launch with ETag caching — new release notes propagate without an APK update -- Bundled assets serve as instant fallback when offline +- **"What's New"** highlights and **full changelog** fetch from GitHub on launch with ETag caching — release notes propagate without an APK update +- Bundled assets serve as instant offline fallback -### Video Player Improvements +### Video Player - **Video load phases** with cancellation support and progress tracking ### Encryption Reliability -- **CTR decryption** now writes to a temporary file and renames on success, preventing data loss on failure +- **CTR decryption** writes to a temporary file and renames on success, preventing data loss on failure - **Progress callback** added to `getVaultedFile` for better UX during decryption ### Per-File Encryption -- **Per-file encryption configuration** — each file can now have its own encryption algorithm and KDF settings +- **Per-file encryption configuration** — each file can have its own encryption algorithm and KDF settings - **Encryption settings prompt** on every import flow, letting users choose per-file options - **Re-encrypt file picker screen** with selection UI and progress tracking, replacing the old dialog - **`derivedKey` parameter** for per-file key derivation @@ -178,11 +173,11 @@ All notable changes to Latch are documented in this file. - Document viewer colors migrated to theme context - File overlay badges unified into a single container - File names and **type-specific icons** shown in grid thumbnails for all file types -- **AnimatedSwitcher** for smooth view mode transitions +- **AnimatedSwitcher** for smooth view-mode transitions ### Encryption Reliability -- **Crash recovery** added to re-encryption journal — interrupted re-encryption operations can now resume safely -- Encrypt output now written to a **temp file** and renamed on success, preventing corrupt vaulted files on failure +- **Crash recovery** added to re-encryption journal — interrupted re-encryption operations resume safely +- Encrypt output written to a **temp file** and renamed on success, preventing corrupt vaulted files on failure - Temp file cleanup on re-encryption success - **Non-empty final block** handling in GCM decryption worker @@ -197,10 +192,10 @@ All notable changes to Latch are documented in this file. - **AES-256-GCM v2** authenticated encryption format with crypto isolate pool support - **Isolate-based crypto worker pool** for AES encryption/decryption, offloading heavy crypto from the UI thread - **PBKDF2 hashing** moved entirely to Dart isolates — no more direct Pointy Castle dependency on the main thread -- **Dynamic KDF iterations** for password and PIN hashing, stored per-credential with automatic rotation on config changes +- **Dynamic KDF iterations** for password and PIN hashing, stored per-credential with rotation on config changes - **Decoy credential migration**: KDF iteration counts stored per credential, rotate when vault settings change - **Re-encryption warning dialog**: detailed risk explanations shown before the confirmation dialog -- **`needsMigration`** field added to `VaultedFile` for tracking files that need re-encryption +- **`needsMigration`** field added to `VaultedFile` for tracking files needing re-encryption ### Media Selection & UX - New **hold-to-action** gesture: long-press triggers an action sheet instead of immediate selection @@ -300,9 +295,5 @@ All notable changes to Latch are documented in this file. --- > **GitHub Releases Deprecated** -> -> GitHub Releases for Latch are no longer maintained. The app is distributed exclusively through **Google Play Store** as a Closed Beta Test. -> -> To join the Closed Beta, email: `moss_apps@proton.me` > -> Old release APKs and tags are kept for historical reference only. +> Latch is distributed exclusively via **Google Play Closed Beta**. Join: `moss_apps@proton.me`. Old release APKs/tags kept for historical reference. diff --git a/README.md b/README.md index 0d85780..3281c23 100644 --- a/README.md +++ b/README.md @@ -12,306 +12,129 @@ --- -Latch is a secure, private media vault application built with Flutter for Android. It provides a safe space to hide and protect your sensitive photos, videos, and documents from prying eyes, with multiple layers of security including biometric authentication, optional AES-256 encryption, and an auto-kill feature that removes the app from the recent apps list when you leave. - -> **Renamed from Locker to Latch.** The repository directory is still `Locker`; the app and all user-facing references are now **Latch**. - -> **Now on Google Play** — [Download Latch](https://play.google.com/store/apps/details?id=com.mossapps.locker). - -## Key Features - -### Core Functionality -- **Media Vault**: Securely hide images, videos, and documents from your device gallery -- **Gallery Import**: Import media directly from your device gallery with the option to delete originals -- **Camera Integration**: Capture photos and videos directly into the vault -- **Document Support**: Store and view PDFs, Office documents (Word, Excel, PowerPoint), and text files -- **Custom Media Picker**: Built-in media picker with album browsing and multi-select support -- **Custom Document Picker**: File browser for selecting documents from device storage -- **Media Scanner**: Automatic duplicate detection when importing files -- **Backup & Restore**: Create local backups of your vault and restore when needed -- **Compression Options**: Choose compression levels for media files to save storage space - -### Organization -- **Folders**: Create nested folder structures with drag-and-drop import, breadcrumb navigation, and expandable tree view -- **File Explorer**: Browse vault contents with grid/list views, filtering, multi-select, and sort controls -- **Albums**: Create custom albums to organize your hidden files -- **Tags**: Add color-coded tags to files for easy categorization and filtering -- **Favorites**: Mark files as favorites for quick access -- **Search**: Find files by name, tags, type, date, or other criteria -- **Sorting**: Multiple sorting options including date, name, size, and type - -### Viewing -- **Image Viewer**: Full-screen image viewing with pinch-to-zoom and slideshow mode -- **Video Player**: Built-in video player with playback controls, speed adjustment, and loop options -- **Song Player**: Built-in audio playback with external app handoff support -- **Document Viewer**: Native PDF rendering and Office document conversion for viewing -- **File Export**: Export files to Downloads folder or open with external applications -- **Performance Overlay**: Real-time display of FPS and performance metrics - -### Security Features -- **PIN Authentication**: 6-digit PIN lock with secure storage -- **Password Authentication**: Traditional password protection option -- **Biometric Authentication**: Fingerprint and face recognition support -- **Encryption**: AES-256-GCM (authenticated) and AES-256-CTR (fast) encryption for stored files -- **Encryption Settings**: Choose encryption algorithm, re-encrypt existing files, manage keys -- **PBKDF2 Key Derivation**: Configurable iteration count for master key derivation with salted hashing -- **Auto-Kill**: Automatically removes app from recent apps when leaving -- **Decoy Mode**: Set up a fake vault with a separate PIN to show if forced to unlock -- **Secure Delete**: Overwrite files before deletion to prevent recovery -- **Change Security**: Update PIN or password at any time with verification - -### Theme & Customization -- **Dynamic Accent Colors**: Choose from multiple accent colors (Blue, Purple, Pink, Red, Orange, Teal, Green, Gunmetal) -- **Custom Theme**: Personalize the app's look to match your style -- **Performance Mode**: Adjust frame rate and performance settings for optimal experience -- **Glassmorphism**: Modern unlock screen design with visual effects +Latch is a private media vault for Android, written in Flutter. It hides +photos, videos, and documents behind PIN/password/biometric auth, encrypts +them with AES-256, and removes itself from the recents list when you leave +(auto-kill). + +> Renamed from Locker to Latch. The repo directory is still `Locker`, but +> the app and all user-facing references are **Latch**. +> On Google Play: [Download Latch](https://play.google.com/store/apps/details?id=com.mossapps.locker). + +## Features + +**Vault** +- Import from gallery, camera, or files; optionally delete the originals +- Duplicate detection on import +- Per-file encryption settings and compression +- Local backup and restore + +**Organization** +- Nested folders with drag-and-drop, breadcrumbs, and a tree view +- Albums, color-coded tags, favorites +- Grid/list explorer with filters, multi-select, sorting (date, name, size, type) +- Search across name, tags, type, and date + +**Viewing** +- Image viewer (pinch-zoom, slideshow) +- Video player (speed, loop) +- Song player with Flick handoff +- PDF and Office document viewer +- Export to Downloads or external apps + +**Security** +- PIN or password unlock, with biometrics +- AES-256-GCM (default) or AES-256-CTR, with re-encryption of existing files +- Argon2id master-key wrapping; per-file PBKDF2 +- Auto-kill, screenshot protection, secure delete +- Decoy vault behind a separate PIN + +**Theme** +- Accent colors, custom theming, glassmorphism unlock screen ## Moss Ecosystem -Latch is part of the **Moss ecosystem** by Ultra Electronica, a suite of interconnected apps that share infrastructure and capabilities. - -### Apps in the Ecosystem -- **Latch**: Secure media vault for hiding photos, videos, and documents -- **Flick Player**: High-performance audiophile music player with UAC 2.0 support - -### Cross-App Integration -Latch integrates with Flick Player through platform channels: -- **Playback Handoff**: Latch can hand off audio playback to Flick for advanced audio engine features (EQ, effects, UAC 2.0 DAC output) -- **Shared Infrastructure**: Last.fm scrobbling, adaptive theming, and library scanning are shared across Moss apps - -### Using Latch with Flick -When you want to play an audio file stored in Latch using Flick's advanced audio capabilities: -1. Select the audio file in Latch -2. Choose to open with Flick Player -3. Flick handles playback with its high-performance engine -4. Last.fm scrobbling continues uninterrupted +Latch is part of the Moss ecosystem. **Flick** is the companion music +player (UAC 2.0 DAC support). Latch can hand audio playback to Flick and +return via `locker://return`. Details in the +[Flick Integration Guide](docs/flick_integration.md). ## Technology Stack -### Frontend (Flutter) +**Flutter** + | Package | Purpose | |---------|---------| | `flutter_riverpod` | State management | -| `flutter_secure_storage` | Secure storage for credentials and metadata | +| `flutter_secure_storage` | Credentials and metadata | | `pointycastle` | AES-256 encryption | -| `photo_manager` | Media gallery access and import | +| `photo_manager` | Gallery access and import | | `pdfrx` | Native PDF rendering | -| `syncfusion_flutter_pdf` | Office document conversion (Word/Excel/PowerPoint) | +| `syncfusion_flutter_pdf` | Office document conversion | | `flutter_image_compress` | Image compression | | `video_compress` | Video compression | -| `archive` | ZIP/RAR/7Z archive support | -| `permission_handler` | Runtime permission management | -| `local_auth` | Biometric authentication (fingerprint/face) | -| `camera` | Camera integration for photo/video capture | -| `just_audio` | Audio playback with external handoff support | -| `in_app_update` | Google Play In-App Updates | - -### Backend (Kotlin/Android) -| Component | Purpose | -|-----------|---------| -| `MainActivity.kt` | Auto-kill feature, performance settings, content URI handling | -| `AutoKillService` | Removes app from recent apps when backgrounded | -| `PermissionHandler` | Android runtime permission management | +| `archive` | ZIP/RAR/7Z support | +| `permission_handler` | Runtime permissions | +| `local_auth` | Biometric authentication | +| `camera` | Photo/video capture | +| `just_audio` | Audio playback | +| `in_app_update` | Play Store updates | -## Project Structure +**Kotlin / Android** -``` -Locker/ -├── lib/ # Flutter/Dart source -│ ├── main.dart # Application entry point -│ ├── autofill_app.dart # Autofill entry point (separate app mode) -│ ├── crypto/ # Pure, testable crypto primitives -│ │ ├── aes_gcm_cipher.dart # AES-256-GCM one-shot -│ │ ├── aes_ctr_cipher.dart # AES-256-CTR one-shot -│ │ ├── key_derivation.dart # PBKDF2 + Argon2id + salt/IV generation -│ │ ├── header_codec.dart # Magic bytes + format detection + headers -│ │ └── key_wrap.dart # AEAD key wrap/unwrap -│ ├── models/ # Data models -│ │ ├── vaulted_file.dart # Vaulted file model -│ │ ├── vault_folder.dart # Folder model -│ │ ├── album.dart # Album and tag models -│ │ ├── note.dart # Note model -│ │ ├── password_entry.dart # Password entry model -│ │ ├── encryption_algorithm.dart # Encryption algorithm enum -│ │ └── accent_color.dart # Accent color options -│ ├── providers/ # Riverpod state providers -│ │ ├── vault_providers.dart # Vault state management -│ │ ├── note_providers.dart # Notes state -│ │ ├── password_providers.dart # Passwords state -│ │ ├── theme_provider.dart # Theme management -│ │ ├── performance_provider.dart # Performance settings -│ │ └── explorer_providers.dart # Explorer state management -│ ├── screens/ # UI screens -│ │ ├── unlock_screen.dart # Authentication unlock screen -│ │ ├── gallery_vault_screen.dart # Gallery import screen -│ │ ├── vault_explorer_screen.dart # Vault explorer with folder management -│ │ ├── media_viewer_screen.dart # Image/video viewer -│ │ ├── document_viewer_screen.dart # PDF/Office document viewer -│ │ ├── song_player_screen.dart # Audio player screen -│ │ ├── note_list_screen.dart # Notes list + folders -│ │ ├── note_editor_screen.dart # Note editor -│ │ ├── password_list_screen.dart # Password manager -│ │ ├── password_editor_screen.dart # Password entry editor -│ │ ├── autofill_selection_screen.dart # Autofill target picker -│ │ ├── folders_screen.dart # Folder management screen -│ │ ├── folder_detail_screen.dart # Folder detail with file management -│ │ ├── encryption_settings_screen.dart # Encryption algorithm settings -│ │ ├── vault_settings_screen.dart # Vault configuration -│ │ └── ... # Setup, backup, camera, tags, albums, etc. -│ ├── services/ # Business logic services -│ │ ├── auth_service.dart # Authentication handling -│ │ ├── encryption_service.dart # AES-256-GCM/CTR facade (delegates to crypto/) -│ │ ├── vault_service.dart # Core vault operations -│ │ ├── backup_service.dart # Backup and restore -│ │ ├── crypto_isolate_pool.dart # Background crypto worker pool -│ │ ├── note_service.dart # Encrypted notes -│ │ ├── password_service.dart # Password manager -│ │ ├── decoy_service.dart # Decoy vault -│ │ ├── file_import_service.dart # Import orchestration -│ │ ├── file_open_service.dart # Centralized file opening -│ │ └── ... # Compression, permissions, updates, etc. -│ ├── themes/ # App theming -│ │ ├── app_colors.dart # Accent colors + FileTypeColors -│ │ └── app_theme.dart # Theme configuration -│ ├── utils/ # Utilities -│ │ ├── path_utils.dart # Centralized paths (downloads, source roots) -│ │ ├── toast_utils.dart # SnackBar-based toasts -│ │ ├── secure_compare.dart # Constant-time comparison -│ │ ├── argon2_isolate.dart # Argon2id via isolate -│ │ ├── pbkdf2_isolate.dart # PBKDF2 via isolate -│ │ └── ... # Navigator key, clipboard, responsive, etc. -│ └── widgets/ # Reusable widgets -│ ├── operation_progress_sheet.dart # Vault op progress dialog -│ ├── adaptive_logo.dart # Light/dark logo -│ ├── explorer_file_grid.dart # Explorer grid widget -│ ├── explorer_toolbar.dart # Explorer toolbar widget -│ ├── folder_breadcrumb_widget.dart # Breadcrumb navigation -│ ├── folder_tree_widget.dart # Expandable folder tree -│ └── ... # Note card, action sheets, dialogs, etc. -├── android/ # Android platform code -│ └── app/src/main/kotlin/com/mossapps/locker/ -│ └── MainActivity.kt # Auto-kill, performance, content URI handling -├── assets/ # Static assets -│ ├── banner_locker.png # App banner -│ └── ... -├── docs/ # Architecture documentation -│ ├── architecture_media.md # Media encryption/compression design -│ ├── unlock_autofill.md # Unlock credential autofill design -│ └── flick_integration.md # Flick Player integration guide -└── pubspec.yaml # Flutter dependencies -``` +| Component | Purpose | +|-----------|---------| +| `MainActivity.kt` | Auto-kill, performance settings, content URI handling | +| `AutoKillService` | Removes app from recents when backgrounded | +| `PermissionHandler` | Runtime permission management | ## Getting Started -### Prerequisites -- Flutter SDK 3.4.4 or higher -- Dart SDK (included with Flutter) -- Android SDK with API level 36 -- Java Development Kit (JDK) 17 -- Android device with Android 8.0 (API 26) or higher - -### Installation - -> **Latch is available on the [Google Play Store](https://play.google.com/store/apps/details?id=com.mossapps.locker)** and via [GitHub Releases](https://github.com/moss-apps/Latch/releases). +**Prerequisites:** Flutter 3.4.4+, Android SDK API 36, JDK 17, Android 8.0 (API 26)+ device. -### Running ```bash -# Run in debug mode -flutter run - -# Run on a specific device -flutter run -d -``` - -### Building -```bash -# Debug build -flutter build apk --debug - -# Release build +flutter pub get +flutter run # debug +flutter run -d # specific device flutter build apk --release --obfuscate --split-debug-info=./build/symbols +flutter pub run flutter_launcher_icons # regenerate icons (optional) ``` -#### Building from Source -1. Clone the repository: - ```bash - git clone https://github.com/moss-apps/Latch.git - cd Locker - ``` -2. Install dependencies: - ```bash - flutter pub get - ``` -3. Generate launcher icons (optional): - ```bash - flutter pub run flutter_launcher_icons - ``` -4. Build using the commands above - -## Platform-Specific Notes - -### Android -Latch is designed exclusively for Android, using Flutter for UI and Kotlin for native features: -- **Auto-Kill**: Uses Android's activity lifecycle to remove the app from recent tasks when backgrounded -- **Biometrics**: Leverages Android's BiometricPrompt API for fingerprint/face authentication -- **Media Access**: Uses `photo_manager` to access device gallery media (requires storage permissions) -- **Camera**: Integrates with Android's Camera API for photo/video capture - -#### Permissions +## Permissions + | Permission | Purpose | |------------|---------| -| READ_EXTERNAL_STORAGE | Access files on device (Android 12 and below) | -| WRITE_EXTERNAL_STORAGE | Write files to device (Android 12 and below) | -| READ_MEDIA_IMAGES | Access images (Android 13+) | -| READ_MEDIA_VIDEO | Access videos (Android 13+) | -| MANAGE_EXTERNAL_STORAGE | Full file access for hiding/unhiding (Android 11+) | +| READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE | File access (Android ≤12) | +| READ_MEDIA_IMAGES / READ_MEDIA_VIDEO | Media access (Android 13+) | +| MANAGE_EXTERNAL_STORAGE | Full file access for hide/unhide (Android 11+) | | CAMERA | Capture photos and videos | -| RECORD_AUDIO | Record audio with video | +| RECORD_AUDIO | Audio with video | | USE_BIOMETRIC | Biometric authentication | ## Architecture -Latch follows a service-based architecture with clear separation of concerns: -- **Services Layer**: Business logic for authentication, encryption, file operations, and media handling -- **Providers Layer**: Riverpod providers for reactive state management across UI components -- **Screens**: Feature-specific UI screens for authentication, vault management, settings, and media viewing -- **Native Backend**: Kotlin code for Android-specific features like auto-kill, permissions, and camera access -All sensitive data (PIN, passwords, encryption keys) is stored via `flutter_secure_storage` in Android's Keystore system. +Service-based, split into four layers: **Services** (auth, encryption, +file ops, media), **Providers** (Riverpod reactive state), **Screens** +(feature UI), and a **Kotlin backend** (auto-kill, permissions, camera). +All sensitive data (PINs, passwords, keys) goes through +`flutter_secure_storage` into Android's Keystore. See +[Media Encryption & Compression](docs/architecture_media.md). ## Documentation -Additional documentation available: -- [Architecture Diagram](docs/architecture_media.md) - Detailed system architecture design covering compression, encryption, and file operations -- [Unlock Autofill](docs/unlock_autofill.md) - How the opt-in unlock credential autofill delegation works and its security model -- [Flick Integration Guide](docs/flick_integration.md) - Contract and implementation notes for making Flick a first-class Latch playback companion + +- [Media Encryption & Compression](docs/architecture_media.md): compression, encryption, file ops +- [Unlock Autofill](docs/unlock_autofill.md): unlock credential autofill and its security model +- [Flick Integration Guide](docs/flick_integration.md): Flick playback handoff contract + +## Contributing + +Fork → feature branch → `flutter analyze` + pass tests → pull request. +Follow the Dart style guide. ## License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -Latch is purely open-source and free. There are no premium features, ads, or paid components. +MIT, see [LICENSE](LICENSE). Free, no ads, no paid features. ## Contributors -- [@ultraelectronica](https://github.com/ultraelectronica) (Project creator) -## Contributing -Contributions are welcome. Please ensure all changes pass linting and testing before submitting pull requests. - -### Code Style -- Follow the Dart style guide -- Run `flutter analyze` before submitting -- Ensure all existing tests pass - -### Steps -1. Fork the repository -2. Create a feature branch: - ```bash - git checkout -b feature/your-feature-name - ``` -3. Make your changes and commit: - ```bash - git commit -m "Add your feature description" - ``` -4. Push to your fork: - ```bash - git push origin feature/your-feature-name - ``` -5. Open a Pull Request +- [@ultraelectronica](https://github.com/ultraelectronica) (creator) diff --git a/assets/privacy_policy.md b/assets/privacy_policy.md index e5c8cbd..10f5c65 100644 --- a/assets/privacy_policy.md +++ b/assets/privacy_policy.md @@ -2,13 +2,11 @@ **Last updated: May 4, 2026** -## Our Commitment +Latch is built on-device-first. It does not collect, store, transmit, or +share any personal information. Everything you do in Latch stays on your +device, and your vault key never leaves it. -Latch is built with privacy as its core principle. We believe your personal data should stay on your device, not on our servers. This app does not collect, store, transmit, or share any personal information. - -## Data We Do Not Collect - -Latch does not collect any data whatsoever. Specifically: +## Data we do not collect - No personal information (name, email, phone number, etc.) - No usage analytics or telemetry @@ -19,57 +17,53 @@ Latch does not collect any data whatsoever. Specifically: - No contacts, messages, or call logs - No browsing history or app usage data -Everything you do in Latch stays on your device. - -## Camera Access - -Latch may request camera permission only when you choose to scan a QR code or take a photo to import into your vault. The camera is used solely for these purposes: - -- QR code scanning for quick vault access -- Capturing photos to store in your vault - -Camera access is never used in the background. No photos or video from your camera are transmitted to any server. All captured media is stored locally and encrypted on your device. - -## Media & Files +## Camera access -Latch is a secure media vault. Files you import into Latch are encrypted using AES-256 encryption and stored locally on your device. We do not: +Requested only when you scan a QR code or take a photo to import into your +vault. Never used in the background; captured media is stored locally and +encrypted, never transmitted. -- Upload your files to any cloud or server -- Scan or analyze your files -- Share your files with any third party -- Access your files without your explicit action +## Media & files -Your vault key never leaves your device. +Files you import are AES-256 encrypted and stored locally. Latch never +uploads, scans, analyzes, shares, or accesses your files without your +explicit action. -## Storage Permission +## Storage permission -Latch requires storage permission to read files you want to vault and to save encrypted files to your device. This permission is used only when you explicitly choose to import or export files. +Required to read files you want to vault and to write encrypted files to +your device — used only on explicit import/export. -## Biometric Authentication +## Biometric authentication -If you choose to use fingerprint or face unlock, biometric data is handled entirely by your device operating system. Latch only receives a success or failure signal — it never stores or accesses your biometric data. +Biometric data is handled entirely by the OS. Latch only receives a +success/failure signal and never stores or accesses your biometric data. -## Third-Party Services +## Third-party services -Latch does not integrate with any third-party analytics, advertising, or tracking services. The only external connection is: +Latch integrates with no analytics, advertising, or tracking services. The +only external connections are: -- Play Store update check (optional, triggered by you) -- Donation link to Ko-fi (optional, opened in your browser) +- Play Store update check (optional, user-triggered) +- Ko-fi donation link (optional, opened in your browser) -Neither of these transmits any personal data from Latch. +Neither transmits personal data from Latch. -## Local Backups +## Local backups -If you choose to create a local backup, the backup file is saved to a location you select on your device. Latch does not upload backups anywhere. You are responsible for securing your backup files. +Backup files are saved to a location you choose on your device. Latch does +not upload backups anywhere — you are responsible for securing them. -## Children's Privacy +## Children's privacy -Latch does not knowingly collect any information from anyone, including children under 13. Since no data is collected at all, there is no risk of personal information being gathered from minors. +Latch does not knowingly collect any information from anyone, including +children under 13. No data is collected, so none can be gathered from +minors. -## Changes to This Policy +## Changes to this policy -If we update this Privacy Policy, the changes will be reflected in the app. We encourage you to review this policy periodically. +Updates are reflected in the app. Review periodically. ## Contact -If you have any questions about this Privacy Policy, please reach out through the project repository. +Questions about this policy: reach out through the project repository. diff --git a/docs/architecture_media.md b/docs/architecture_media.md index abe70b5..7993152 100644 --- a/docs/architecture_media.md +++ b/docs/architecture_media.md @@ -1,4 +1,4 @@ -# Architecture: Media Encryption & Compression +# Media Encryption & Compression ## System Overview @@ -28,60 +28,36 @@ ``` `EncryptionService` is a facade: it owns key storage (secure storage I/O), -key caching, and file-streaming orchestration, and delegates the actual -crypto to the pure, independently-tested modules in `lib/crypto/`. -Heavy encrypt/decrypt runs on a 2-worker `CryptoIsolatePool` so the UI -thread stays responsive. +key caching, and file-streaming orchestration, delegating crypto to the +pure modules in `lib/crypto/`. Heavy encrypt/decrypt runs on a 2-worker +`CryptoIsolatePool` to keep the UI thread responsive. ## Import Pipeline -``` 1. User selects file(s) from gallery / documents / camera - ↓ -2. FileImportService orchestrates: - ├─ Duplicate detection (MediaScannerService) - ├─ Compression (optional, see below) - ├─ Encryption (streaming, via CryptoIsolatePool) - └─ Index update (VaultService) - ↓ -3. VaultedFile metadata written to vault_file_index -``` +2. `FileImportService` orchestrates: duplicate detection (`MediaScannerService`) → optional compression → streaming encryption (`CryptoIsolatePool`) → index update (`VaultService`) +3. `VaultedFile` metadata written to `vault_file_index` ## Compression -Compression is opt-in per import and handled by `CompressionService`: - -- **Images** — bicubic resize (max 4096×4096) via `flutter_bicubic_resize`, - JPEG quality 95. PNGs passed through uncompressed. -- **Video** — shells out to an external `ffmpeg` binary (`libx264`, CRF 18, - fast preset, AAC 192k). **Note:** no FFmpeg Flutter package is bundled; - this path only works on devices with a system `ffmpeg` binary and is - effectively a no-op on stock Android. `video_compress` is used as the - primary video compression path elsewhere. +Opt-in per import, handled by `CompressionService`: -> The earlier revision of this document described an `ImprovedCompressionService` -> with an isolate-managed FFmpeg pipeline, progress parsing, and a rollback -> stack. That design was never implemented and has been removed from this -> document. - ---- +- **Images**: bicubic resize (max 4096×4096) via `flutter_bicubic_resize`, JPEG quality 95. PNGs pass through uncompressed. +- **Video**: `video_compress` is the primary path. An `ffmpeg` shell-out (`libx264`, CRF 18, AAC 192k) exists but only works where a system `ffmpeg` binary is present, and is effectively a no-op on stock Android. ## Encryption Modes -Latch supports two AES-256 encryption modes, selectable via the Encryption -Settings screen: +Selectable via the Encryption Settings screen: -### AES-256-GCM (Galois/Counter Mode) — default -- **Speed**: Slightly slower due to authentication overhead -- **Integrity**: Authenticated encryption with a 16-byte auth tag -- **Magic bytes**: `0x4C4B5232` (v2 header) -- **Use case**: Default for new media; recommended for security-sensitive vaults +### AES-256-GCM (default) +- 16-byte IV + 16-byte auth tag +- Magic bytes: `0x4C4B5232` (v2 header) +- Authenticated encryption; default for new media -### AES-256-CTR (Counter Mode) -- **Speed**: Fast, no integrity verification -- **Parallelizable**: Encryption and decryption can be parallelized -- **Magic bytes**: `0x4C4B5253` -- **Use case**: Opt-in for performance +### AES-256-CTR +- 16-byte IV, no auth tag +- Magic bytes: `0x4C4B5253` +- Opt-in for performance ``` ┌──────────────────────────────────────────────────────────────┐ @@ -92,7 +68,7 @@ Settings screen: │ │ Source File │ │ │ └──────┬───────┘ │ │ ↓ │ -│ ┌──────────────────────────────────────────────────┐ │ +│ ┌──────────────────────────────────────────────────────────┐│ │ │ Algorithm Selection │ │ │ │ ┌─────────────────┐ ┌─────────────────────┐ │ │ │ │ │ AES-256-CTR │ │ AES-256-GCM │ │ │ @@ -114,19 +90,17 @@ Settings screen: └──────────────────────────────────────────────────────────────┘ ``` ---- - ## Key Derivation ### Master key wrapping (H1) -The vault master key is wrapped by an Argon2id-derived key-wrapping key (KWK) -from the user's credential: +The vault master key is wrapped by an Argon2id-derived key-wrapping key +(KWK) from the user's credential: ``` User Password/PIN - │ - ↓ + │ + ↓ ┌──────────────────────────────┐ │ Argon2id │ │ • iterations: 3 │ @@ -135,7 +109,7 @@ User Password/PIN │ • key length: 32 bytes │ └──────────────┬───────────────┘ ↓ - 256-bit Key-Wrapping Key (KWK) + 256-bit Key-Wrapping Key (KWK) │ ↓ ┌──────────────────────────────┐ @@ -144,79 +118,50 @@ User Password/PIN └──────────────────────────────┘ ``` -Legacy vaults (raw master key) are transparently migrated on next unlock: -the raw key is read, wrapped, then deleted. +Legacy vaults (raw master key) migrate transparently on next unlock: raw +key is read, wrapped, then deleted. ### Per-file key derivation (PBKDF2) -Each encrypted file derives a per-file key from the master key via PBKDF2: +Each encrypted file derives a per-file key from the master key: ``` Master Key - │ - ↓ + │ + ↓ ┌──────────────────────────────┐ │ PBKDF2-HMAC-SHA256 │ -│ • Iteration count: 600,000 │ +│ • Iterations: 600,000 │ │ • Salt: 32-byte random │ │ • Key length: 32 bytes │ │ • Configurable iterations │ └──────────────┬───────────────┘ ↓ - Per-file 256-bit Key + Per-file 256-bit Key ``` -### Salt Generation -- Each credential and file gets a unique 32-byte random salt -- Salt is stored alongside the derived hash for verification -- Prevents rainbow table attacks across vaults - -### Legacy Migration -- Pre-PBKDF2 vaults stored passwords using plain SHA-256 -- On unlock, legacy hashes are detected and automatically migrated to PBKDF2 -- Decoy credentials migrated with separate salted hashing - ---- +- Each credential and file gets a unique 32-byte random salt, stored alongside the derived hash, preventing rainbow-table attacks across vaults. +- Pre-PBKDF2 vaults stored passwords as plain SHA-256; on unlock these are detected and auto-migrated to PBKDF2 (decoy credentials migrated with separate salted hashing). ## Re-Encryption (Algorithm Migration) -When the user changes the encryption algorithm (e.g., CTR → GCM), existing -vault files are re-encrypted: - -``` -┌──────────────────────────────────────────────────────────────┐ -│ Re-Encryption Flow │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ 1. Select new algorithm in Encryption Settings │ -│ ↓ │ -│ 2. Decrypt each vault file with old algorithm │ -│ ↓ │ -│ 3. Encrypt plaintext with new algorithm │ -│ ↓ │ -│ 4. Verify integrity (GCM auth tag validation) │ -│ ↓ │ -│ 5. Replace vault file with new-format file │ -│ ↓ │ -│ 6. Update vault index metadata │ -│ ↓ │ -│ 7. Secure-delete temporary plaintext │ -│ │ -│ Rollback: If any file fails, operation is aborted │ -│ and original files are preserved. │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - -### ReEncryptNotifier -- Manages re-encryption state across all vault files -- Provides progress tracking per file -- Reports success, failure, and progress to UI - -### Security Guarantees -- Plaintext intermediates are written to app-private temp - (`getApplicationDocumentsDirectory()/.locker_temp/`), not system temp, - and `secureDelete`d on completion or failure -- Small files use the in-memory GCM path (no plaintext on disk at all) -- On cancellation or failure, the original vault files are untouched -- GCM auth tag validation catches corruption during migration +Changing the encryption algorithm (e.g., CTR → GCM) re-encrypts existing +vault files: + +1. Select new algorithm in Encryption Settings. +2. Decrypt each vault file with the old algorithm. +3. Encrypt plaintext with the new algorithm. +4. Verify integrity (GCM auth tag). +5. Replace the vault file with the new-format file. +6. Update vault index metadata. +7. Secure-delete the temporary plaintext. + +`ReEncryptNotifier` tracks per-file progress and reports success/failure to +the UI. On any failure the operation aborts and original files are +untouched. + +### Security guarantees +- Plaintext intermediates go to app-private temp (`getApplicationDocumentsDirectory()/.locker_temp/`), never system temp, and are `secureDelete`d on completion or failure. +- Small files use the in-memory GCM path (no plaintext on disk). +- On cancellation or failure, original vault files are untouched. +- GCM auth-tag validation catches corruption during migration. diff --git a/docs/flick_integration.md b/docs/flick_integration.md index 8435043..290e42c 100644 --- a/docs/flick_integration.md +++ b/docs/flick_integration.md @@ -1,45 +1,39 @@ -# Flick Integration Guide +# Flick Integration -## Goal +Flick plays audio handed off from Latch. Latch sends a hidden song via a +standard Android audio `VIEW` intent; Flick streams it without copying to +public storage and exposes a `Back to Latch` action that returns to the +running Latch task. -Flick should behave like a first-class playback companion for Latch: +## Contract -- Latch hands Flick a hidden song with a normal Android audio `VIEW` intent. -- Flick plays that URI directly without copying it into public storage. -- Flick exposes an explicit `Back to Latch` action. -- Returning from Flick should foreground the existing Latch task when it is already running. +| Item | Value | +|---|---| +| Latch package | `com.mossapps.locker` | +| Flick package | `com.mossapps.flick` | +| Return URI | `locker://return` | +| Latch launch mode | `singleTask` | +| Return intent filter | `ACTION_VIEW` on `locker://return` | -This keeps the two apps feeling like one ecosystem instead of two unrelated apps. +- Opening `locker://return` foregrounds a running Latch task, or cold-starts Latch. +- Latch targets Flick's package directly, skipping the generic app chooser. +- No custom action or permission is required from Flick for v1; standard `VIEW` for audio is enough. -## Latch Contract +## Flick requirements -Latch now exposes these Android integration points: +| # | Requirement | Detail | +|---|---|---| +| 1 | Accept audio `VIEW` intents | Exported playback activity, `content://` scheme, MIME `audio/*`. Do not require custom permissions. | +| 2 | Stream the URI directly | Do not copy to shared storage, rescan into the media library, or assume the URI is permanent. Release URI + player on completion. | +| 3 | Preserve Latch's privacy model | No background indexing, public caching, cloud sync, or gallery-visible thumbnails of Latch media. Cache for playback only inside Flick's private storage; clean up aggressively. | +| 4 | Provide a `Back to Latch` action | Visible in the player UI (and error states). Do not rely on back-stack behavior alone. | -- Locker package: `com.mossapps.locker` -- Flick package: `com.mossapps.flick` -- Return URI: `locker://return` -- Main activity launch mode: `singleTask` -- Return intent filter: `ACTION_VIEW` on `locker://return` - -What that means for Flick: - -- If Latch is already open in the background, opening `locker://return` will bring that existing task forward. -- If Latch is not running, Android will cold-start it. -- Latch does not need a custom action for v1. Standard Android `VIEW` for audio is enough. - -## What Flick Should Implement - -### 1. Accept standard Android audio `VIEW` intents - -Flick should expose an exported playback activity that can handle audio sent from Latch. - -Recommended manifest shape: +### Manifest ```xml - @@ -49,18 +43,7 @@ Recommended manifest shape: ``` -Notes: - -- Supporting `content://` is the important part. -- Keep MIME support broad: `audio/*`. -- You can add narrower types later if you want analytics or specialized handling. -- Do not require custom permissions from Latch. - -## 2. Read the incoming URI directly - -Latch will hand Flick a temporary readable audio URI. Flick should stream from it instead of copying it into gallery-visible storage. - -If Flick uses Media3 / ExoPlayer, the flow should look like this: +### Playback (Media3 / ExoPlayer) ```kotlin val audioUri = intent?.data ?: return @@ -71,7 +54,7 @@ player.prepare() player.playWhenReady = true ``` -If Flick does any preflight validation, do it through `ContentResolver`: +URI readability preflight via `ContentResolver`: ```kotlin contentResolver.openAssetFileDescriptor(audioUri, "r")?.use { @@ -79,36 +62,7 @@ contentResolver.openAssetFileDescriptor(audioUri, "r")?.use { } ``` -Important behavior: - -- Do not move the file into shared storage. -- Do not rescan it into the media library. -- Do not assume the URI is permanent. -- Release the URI and player resources when playback is done. - -## 3. Preserve Latch's privacy model - -Flick should treat Latch-supplied media as temporary private content. - -Recommended rules on the Flick side: - -- No background indexing of Latch media. -- No automatic caching to public folders. -- No automatic cloud sync for the handed-off file. -- No thumbnail export into gallery-visible locations. -- If caching is needed for playback stability, keep it inside Flick's private app storage and clean it up aggressively. - -## 4. Add a dedicated `Back to Latch` action - -Do not rely only on Android back-stack behavior. It may feel okay in some cases, but explicit return is more reliable and intentional. - -Flick should expose a visible action in the player UI such as: - -- `Back to Latch` -- `Return to Latch` -- `Done in Flick` - -Recommended implementation: +### Return to Latch ```kotlin private fun returnToLocker(context: Context) { @@ -120,67 +74,19 @@ private fun returnToLocker(context: Context) { addCategory(Intent.CATEGORY_BROWSABLE) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) } - context.startActivity(intent) } ``` -Why this is the right shape: - -- `package = "com.mossapps.locker"` makes the return deterministic. -- `locker://return` matches Latch's manifest contract. -- `CLEAR_TOP` and `SINGLE_TOP` help Android reuse the running Latch activity instead of creating unnecessary duplicates. - -If you want to be defensive, wrap the launch in a `try/catch` and fall back to a normal `finish()` if Latch is unavailable. - -## 5. Package-targeted handoff - -Latch now knows Flick's package name and can target it directly: - -- Flick package: `com.mossapps.flick` -- Latch can detect whether Flick is installed -- Latch can show a dedicated `Play with Flick` action -- Latch can skip the generic app chooser when the user explicitly wants Flick - -That makes the handoff deterministic and keeps the two-app flow feeling tighter. +`package` makes the return deterministic; `CLEAR_TOP` + `SINGLE_TOP` reuse the running Latch activity. Wrap in `try/catch` and fall back to `finish()` if Latch is unavailable. -Because of that, Flick's package identifier should now be treated as part of the integration contract. - -## Recommended UX Flow - -1. User taps a song in Latch. -2. Latch can either play it internally or hand it to Flick. -3. Flick opens directly on a playback screen. -4. Flick makes it obvious the track came from Latch. -5. User taps `Back to Latch`. -6. Flick launches `locker://return`. -7. Latch comes back to the foreground in its existing state. - -Good small UX touches in Flick: - -- Show a small `Opened from Latch` label. -- Keep the return action visible without burying it in a menu. -- If playback fails, offer `Back to Latch` in the error state too. - -## Test Checklist - -Run these checks before calling the integration done: +## Test checklist 1. Launch an MP3 from Latch into Flick. -2. Verify Flick can read the URI without copying it to public storage. -3. Verify playback works for at least `mp3`, `m4a`, and `ogg`. -4. Tap `Back to Latch` and confirm the existing Latch task returns. -5. Confirm Latch is not duplicated in recents. -6. Confirm the handed-off song does not appear in gallery/music scanners because of Flick. -7. Confirm closing Flick does not leave stale temp playback state behind. - -## Latch-side implementation status - -Latch is configured to support this integration now: - -- `locker://return` is registered in Latch's manifest -- Latch uses `singleTask` so the existing task can be foregrounded again -- Latch can query `com.mossapps.flick` -- Latch can offer a dedicated `Play with Flick` action for songs when Flick is installed +2. Flick reads the URI without copying it to public storage. +3. Playback works for `mp3`, `m4a`, and `ogg`. +4. `Back to Latch` returns the existing Latch task; Latch is not duplicated in recents. +5. The handed-off song does not appear in gallery/music scanners. +6. Closing Flick leaves no stale temp playback state. -The remaining work is fully on Flick's side: accept the URI, play it, and provide the explicit return affordance. +UX touches: show an `Opened from Latch` label; keep the return action visible (not buried in a menu); offer it in the error state too. diff --git a/docs/unlock_autofill.md b/docs/unlock_autofill.md index d95bf57..e7dab64 100644 --- a/docs/unlock_autofill.md +++ b/docs/unlock_autofill.md @@ -1,46 +1,37 @@ # Unlock Autofill Latch can delegate its unlock credential (PIN or password) to the system -autofill provider — Google Password Manager, Bitwarden, Samsung Pass, etc. -External password managers can **fill** the unlock field and **save** the -credential after a successful unlock. - -The feature is **off by default**. Enable in Security Settings → "Autofill -Credential", behind a 10-second countdown warning dialog. +autofill provider (Google Password Manager, Bitwarden, Samsung Pass, etc.). +External managers can **fill** the unlock field and **save** the credential +after a successful unlock. The feature is **off by default**; enable it in +Security Settings → "Autofill Credential", behind a 10-second countdown +warning. ## How it works -### Fill (autofill hints) - +### Fill When enabled, the unlock screen's `TextField` (password mode) and `PinInputWidget`'s hidden `TextField` (PIN mode) get -`autofillHints: [AutofillHints.password]`. Android's autofill framework -exposes these fields to registered autofill services, which can offer to -fill a saved credential into the field. +`autofillHints: [AutofillHints.password]`, exposing them to registered +autofill services. ### Save prompt +Both auth widgets are wrapped in an `AutofillGroup` +(`onDisposeAction: AutofillContextAction.commit`): -Both auth widgets are wrapped in an `AutofillGroup` (default -`onDisposeAction: AutofillContextAction.commit`): +- **Successful unlock** → `pushReplacement` disposes the group with commit → password manager shows "Save password?". +- **Failed unlock** → screen stays → no disposal → no save prompt. +- **App exit** → group disposes → save prompt may appear (OS-dependent). -- **Successful unlock** → `pushReplacement` replaces the screen → the - `AutofillGroup` disposes with commit → the autofill framework sees the - committed field values → password manager shows "Save password?". -- **Failed unlock** → the screen stays → no disposal → no save prompt. -- **App exit from unlock** → group disposes → save prompt may or may not - appear (OS-dependent). +## Files -## Implementation +- `lib/services/auth_service.dart`: `isUnlockAutofillEnabled()` / `setUnlockAutofillEnabled()` +- `lib/widgets/pin_input_widget.dart`: `autofillEnabled` param → `autofillHints` on hidden `TextField` +- `lib/screens/unlock_screen.dart`: `_autofillEnabled` state, `_wrapAutofill()` with `AutofillGroup` +- `lib/screens/change_security_screen.dart`: toggle card + `_AutofillWarningDialog` (10s countdown) -``` -lib/services/auth_service.dart isUnlockAutofillEnabled() / setUnlockAutofillEnabled() -lib/widgets/pin_input_widget.dart autofillEnabled param → autofillHints on hidden TextField -lib/screens/unlock_screen.dart _autofillEnabled state, _wrapAutofill() with AutofillGroup -lib/screens/change_security_screen.dart toggle card + _AutofillWarningDialog (10s countdown) -``` - -The setting is persisted in `FlutterSecureStorage` under the key -`unlock_autofill_enabled` — same pattern as the biometric toggle. +Setting persisted in `FlutterSecureStorage` under `unlock_autofill_enabled` +(same pattern as the biometric toggle). ## Security model @@ -49,11 +40,8 @@ The setting is persisted in `FlutterSecureStorage` under the key | **Biometric unlock** (default) | KWK encrypted inside Android Keystore, never exposed | | **Autofill unlock** (opt-in) | Credential handed to the chosen autofill provider outside Latch's enclave | -Autofill delegates the "something you know" factor to a third-party -password manager. A compromised autofill provider, a malicious app with -autofill permissions, or a phishing autofill dialog could leak the vault -unlock credential. - -The 10-second countdown and explicit warning text surface these risks -before activation. Biometric unlock remains the recommended convenience -path and is placed above autofill in the security settings UI. +Autofill delegates the "something you know" factor to a third-party password +manager. A compromised provider, a malicious app with autofill permissions, +or a phishing autofill dialog could leak the vault unlock credential. The +10-second countdown surfaces these risks before activation. Biometric unlock +remains the recommended path and sits above autofill in the security UI.