Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
67b5cfc
Move search and sort actions into overflow menu
ultraelectronica Jun 22, 2026
f46e37d
Refactor encryption service to extract crypto primitives
ultraelectronica Jun 22, 2026
1e892c9
Add forceReload parameter to vault cache loaders
ultraelectronica Jun 22, 2026
4e6a5b2
Add stateless AES-256-CTR cipher primitives
ultraelectronica Jun 22, 2026
261040d
Add AES-256-GCM cipher with stateless encrypt/decrypt primitives
ultraelectronica Jun 22, 2026
16a16c0
Add HeaderCodec class for encrypted file format detection
ultraelectronica Jun 22, 2026
3f59465
Add key derivation module with PBKDF2 and Argon2id support
ultraelectronica Jun 22, 2026
c7dcdae
Add AEAD key-wrapping module for vault master key
ultraelectronica Jun 22, 2026
c300501
Add comprehensive tests for crypto modules
ultraelectronica Jun 22, 2026
75b7262
Remove Firebase dependencies from Android Gradle config
ultraelectronica Jun 22, 2026
d222d6f
Remove outdated locker logo asset
ultraelectronica Jun 22, 2026
0b41a47
Update architecture docs for media encryption and compression
ultraelectronica Jun 22, 2026
d39c8ff
Remove unused _importFromDocuments method
ultraelectronica Jun 22, 2026
46b1543
Copy a local list of files to avoid mutating the caller's list on delete
ultraelectronica Jun 22, 2026
8b07ec1
Improve `resetAuth` doc comment with full scope and warning
ultraelectronica Jun 22, 2026
4c42a18
Add margin, shape, and elevation to SnackBar
ultraelectronica Jun 22, 2026
f7de2bb
Remove unused performance and compression widgets
ultraelectronica Jun 22, 2026
cd16ece
Remove unused UI components
ultraelectronica Jun 22, 2026
17578b8
Remove obsolete platform entries from .metadata
ultraelectronica Jun 22, 2026
07c82e2
Bump version to 0.14.4-beta.5 with security hardening and password vault
ultraelectronica Jun 22, 2026
7487a64
Add autofill credential toggle with security warning dialog
ultraelectronica Jun 22, 2026
67f791c
Add autofill support for PIN and password inputs
ultraelectronica Jun 22, 2026
f938d7c
Add unlock autofill preference storage
ultraelectronica Jun 22, 2026
647af00
Add autofillEnabled parameter to PinInputWidget
ultraelectronica Jun 22, 2026
276bf89
Add documentation for autofill unlock credential delegation
ultraelectronica Jun 22, 2026
cf19e05
Bump version to 0.15.0-beta.1+16
ultraelectronica Jun 22, 2026
624cec4
Add install source method channel to MainActivity
ultraelectronica Jun 22, 2026
ae2bf02
Remove unused in_app_update import and update provider type
ultraelectronica Jun 22, 2026
e020deb
Add tests for pre-release version ordering
ultraelectronica Jun 22, 2026
7171746
Display KDF iteration count in millions when applicable
ultraelectronica Jun 22, 2026
57cea0d
Refactor update check to support multiple install sources
ultraelectronica Jun 22, 2026
d894bcb
Add install source detection and GitHub release update flow
ultraelectronica Jun 22, 2026
f2cd488
Add support for GitHub-based updates via url_launcher
ultraelectronica Jun 22, 2026
86ae24d
Add pub_semver as a direct dependency
ultraelectronica Jun 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .metadata
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,6 @@ migration:
- platform: android
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: ios
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: linux
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: macos
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: web
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36
- platform: windows
create_revision: b0850beeb25f6d5b10426284f506557f66181b36
base_revision: b0850beeb25f6d5b10426284f506557f66181b36

# User provided section

Expand All @@ -42,4 +27,3 @@ migration:
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,49 @@

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()`

## 0.14.4-beta.4

### Notes System
Expand Down
65 changes: 50 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

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**.

> **Closed Beta** — Latch is currently in a closed beta test. To join, email `moss_apps@proton.me`.

## Key Features
Expand Down Expand Up @@ -108,54 +110,87 @@ When you want to play an audio file stored in Latch using Flick's advanced audio
## Project Structure

```
locker/
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
│ │ ├── album.dart # Album and tag models
│ │ ├── vaulted_file.dart # Vaulted file model
│ │ ├── vault_folder.dart # Folder model
│ │ └── encryption_algorithm.dart # Encryption algorithm enum
│ │ ├── 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
│ │ ├── vault_explorer_screen.dart # Vault explorer with folder management
│ │ ├── 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
│ │ ├── 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 encryption/decryption
│ │ ├── encryption_service.dart # AES-256-GCM/CTR facade (delegates to crypto/)
│ │ ├── vault_service.dart # Core vault operations
│ │ ├── backup_service.dart # Backup and restore
│ │ └── flick_integration_service.dart # Flick Player handoff
│ │ ├── 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 color definitions
│ │ ├── 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
│ ├── pin_input_widget.dart # PIN entry widget
│ ├── performance_overlay_widget.dart # FPS overlay
│ ├── 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
│ ├── 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 compression/encryption design
│ ├── architecture_media.md # Media encryption/compression design
│ ├── improvement_roadmap.md # Audit-driven backlog
│ └── flick_integration.md # Flick Player integration guide
└── pubspec.yaml # Flutter dependencies
```
Expand All @@ -165,9 +200,9 @@ locker/
### Prerequisites
- Flutter SDK 3.4.4 or higher
- Dart SDK (included with Flutter)
- Android SDK with API level 34
- Android SDK with API level 36
- Java Development Kit (JDK) 17
- Android device with Android 6.0 (API 23) or higher
- Android device with Android 8.0 (API 26) or higher

### Installation

Expand All @@ -192,7 +227,7 @@ flutter run -d <device-id>
flutter build apk --debug

# Release build
flutter build apk --release
flutter build apk --release --obfuscate --split-debug-info=./build/symbols
```

#### Building from Source
Expand Down
2 changes: 0 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
plugins {
id "com.android.application"
id "org.jetbrains.kotlin.android"
id "com.google.gms.google-services"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
Expand Down Expand Up @@ -76,7 +75,6 @@ android {
}

dependencies {
implementation platform("com.google.firebase:firebase-bom:34.12.0")
implementation "androidx.fragment:fragment:1.6.2"
}

Expand Down
18 changes: 18 additions & 0 deletions android/app/src/main/kotlin/com/mossapps/locker/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class MainActivity: FlutterFragmentActivity() {
private val FLICK_PACKAGE = "com.mossapps.flick"
private val RECORDER_CHANNEL = "com.mossapps.locker/audio_recorder"
private val AMPLITUDE_CHANNEL = "com.mossapps.locker/audio_amplitude"
private val INSTALL_SOURCE_CHANNEL = "com.mossapps.locker/install_source"
private val autoKillPreferences by lazy {
getSharedPreferences(AUTO_KILL_PREFS, MODE_PRIVATE)
}
Expand Down Expand Up @@ -170,6 +171,14 @@ class MainActivity: FlutterFragmentActivity() {
else -> result.notImplemented()
}
}

MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALL_SOURCE_CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getInstallerPackageName") {
result.success(getInstallerPackageName())
} else {
result.notImplemented()
}
}
}

private fun startRecording(path: String, format: String, result: MethodChannel.Result) {
Expand Down Expand Up @@ -291,6 +300,15 @@ class MainActivity: FlutterFragmentActivity() {
}
}

private fun getInstallerPackageName(): String? {
return try {
@Suppress("DEPRECATION")
packageManager.getInstallerPackageName(packageName)
} catch (_: Exception) {
null
}
}

private fun openAudioInFlick(
filePath: String,
mimeType: String?,
Expand Down
1 change: 0 additions & 1 deletion android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.9.1" apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false
id "com.google.gms.google-services" version "4.4.4" apply false
}

include ":app"
Binary file removed assets/locker_logo_512.png
Binary file not shown.
Loading
Loading