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
2903a79
Add animated logo entrance to auth method selection screen
ultraelectronica Jun 30, 2026
74f3f2b
Make setup buttons span full width
ultraelectronica Jun 30, 2026
633ea45
Add cooperative cancellation token for decrypt pipeline
ultraelectronica Jun 30, 2026
0b9ec31
Add cancel token support to isolate file decryption
ultraelectronica Jun 30, 2026
9232002
Refactor file open service with phased progress and cancel support
ultraelectronica Jun 30, 2026
8bf1e2a
Add version skipping and deduplicate update emission
ultraelectronica Jun 30, 2026
ce41125
Add cancel token support to getVaultedFile method
ultraelectronica Jun 30, 2026
a64a1f3
Migrate AuthMethodCard to use context-based theming
ultraelectronica Jun 30, 2026
30aeb6b
Add transparency section to README
ultraelectronica Jun 30, 2026
87fc447
Add skip version feature to update dialog
ultraelectronica Jun 30, 2026
756adb8
Add animated Latch logo widget and welcome screen tests
ultraelectronica Jun 30, 2026
c420ec1
Reduce animation duration and stagger span in Latch logo
ultraelectronica Jun 30, 2026
044f573
Rename explorer view modes from sidebar/navigation to grid/list
ultraelectronica Jun 30, 2026
a60b732
Add category filter bottom bar to gallery vault
ultraelectronica Jun 30, 2026
9b74e6e
Remove sidebar view and resizer widget
ultraelectronica Jun 30, 2026
8a85a93
Add list view mode to explorer file grid
ultraelectronica Jun 30, 2026
a85002d
Rename explorer view modes from navigation/sidebar to list/grid
ultraelectronica Jun 30, 2026
0e0b7c8
Remove FAB elevation across screens
ultraelectronica Jun 30, 2026
0987dec
Remove unused re-encryption feature
ultraelectronica Jun 30, 2026
07cb126
Extract file info into reusable FileInfoSheet widget
ultraelectronica Jun 30, 2026
e95a243
Add encrypt and remove encryption buttons to settings screen
ultraelectronica Jun 30, 2026
2d8acdb
Extract file info dialog into reusable FileInfoSheet widget
ultraelectronica Jun 30, 2026
703b137
Extract file info sheet into reusable widget
ultraelectronica Jun 30, 2026
5c7815c
Add search filter and select-all scoping to re-encrypt file picker
ultraelectronica Jun 30, 2026
2e627e9
Add encryptFileInPlace and decryptFileInPlace with progress callback
ultraelectronica Jun 30, 2026
a995ed8
Add encryption and decryption operations and improve re-encrypt progress
ultraelectronica Jun 30, 2026
6519d11
Extract file info dialog into FileInfoSheet widget
ultraelectronica Jun 30, 2026
4ab6f02
Add decrypt and reencrypt operations to progress sheet
ultraelectronica Jun 30, 2026
08bae5d
Add encryption management screen for batch encrypt/decrypt
ultraelectronica Jun 30, 2026
fc022ad
Add shared FileInfoSheet bottom sheet widget
ultraelectronica Jun 30, 2026
d1be5d8
Use theme-aware accent and success colors
ultraelectronica Jun 30, 2026
ed2b7db
Remove elevated button shadow in operation progress sheet
ultraelectronica Jun 30, 2026
56946c8
Remove redundant elevation override from button style
ultraelectronica Jun 30, 2026
6345c9b
Remove default elevation from done button
ultraelectronica Jun 30, 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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,5 +136,11 @@ Follow the Dart style guide.
MIT, see [LICENSE](LICENSE). Free, no ads, no paid features.

## Contributors

- [@ultraelectronica](https://github.com/ultraelectronica) (creator)

## Transparency

I build this with [OpenCode](https://opencode.ai). It writes some of the
comments and docs, and it's bailed me out of more than a few nasty-ass bugs.
The code, the design calls, and the fuck-ups are all mine.
18 changes: 14 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ class _AppInitializerState extends ConsumerState<AppInitializer> {
_checkAuthStatus();

final updateService = ref.read(updateServiceProvider);
_updateSub = updateService.onUpdateCheck.listen((update) {
if (update != null) {
_showUpdateDialog(update);
}
_updateSub = updateService.onUpdateCheck.listen((update) async {
if (update == null) return;
if (await updateService.isVersionSkipped(update.latestVersion)) return;
_showUpdateDialog(update);
});
}

Expand Down Expand Up @@ -120,6 +120,16 @@ class _AppInitializerState extends ConsumerState<AppInitializer> {
onPressed: () => Navigator.pop(dialogContext),
child: const Text('Later'),
),
if (!isPlayStore && update.latestVersion != null)
TextButton(
onPressed: () {
ref
.read(updateServiceProvider)
.skipVersion(update.latestVersion!);
Navigator.pop(dialogContext);
},
child: const Text("Don't show again"),
),
FilledButton(
onPressed: () {
Navigator.pop(dialogContext);
Expand Down
14 changes: 7 additions & 7 deletions lib/providers/explorer_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ import 'vault_providers.dart';

/// View modes supported by the vault file explorer
enum ExplorerViewMode {
sidebar,
navigation;
grid,
list;

String get displayName {
switch (this) {
case ExplorerViewMode.sidebar:
return 'Sidebar Tree';
case ExplorerViewMode.navigation:
return 'Grid Navigation';
case ExplorerViewMode.grid:
return 'Grid View';
case ExplorerViewMode.list:
return 'List View';
}
}
}

/// Provider for current explorer view mode
final explorerViewModeProvider = StateProvider<ExplorerViewMode>((ref) {
return ExplorerViewMode.navigation;
return ExplorerViewMode.grid;
});

/// Provider for the currently viewed folder ID (null represents the root vault folder)
Expand Down
59 changes: 1 addition & 58 deletions lib/providers/vault_providers.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_riverpod/legacy.dart';
import '../models/album.dart';
import '../models/encryption_algorithm.dart';
import '../models/vault_folder.dart';
import '../models/vaulted_file.dart';
import '../services/vault_service.dart';
Expand Down Expand Up @@ -494,63 +493,7 @@ final fileCountSummaryProvider = FutureProvider<String>((ref) async {
return '$total files';
});

class ReEncryptProgress {
final bool isInProgress;
final int current;
final int total;
final String? error;

const ReEncryptProgress({
this.isInProgress = false,
this.current = 0,
this.total = 0,
this.error,
});
}

class ReEncryptNotifier extends Notifier<ReEncryptProgress> {
@override
ReEncryptProgress build() {
return const ReEncryptProgress();
}

VaultService get _vaultService => ref.read(vaultServiceProvider);

Future<int> reEncryptVault(EncryptionAlgorithm targetAlgorithm, {Set<String>? fileFilter}) async {
state = const ReEncryptProgress(isInProgress: true, current: 0, total: 0);
try {
final result = await _vaultService.reEncryptVault(
targetAlgorithm,
fileFilter: fileFilter,
onProgress: (current, total) {
state = ReEncryptProgress(
isInProgress: true,
current: current,
total: total,
);
},
);

if (result < 0) {
state = const ReEncryptProgress(error: 'Re-encryption failed');
} else {
state = ReEncryptProgress(current: result, total: result);
}

ref.invalidate(vaultSettingsProvider);
ref.invalidate(vaultNotifierProvider);
return result;
} catch (e) {
state = ReEncryptProgress(error: e.toString());
return -1;
}
}
}

final reEncryptProvider =
NotifierProvider<ReEncryptNotifier, ReEncryptProgress>(() {
return ReEncryptNotifier();
});
enum VaultEncryptionAction { encrypt, removeEncryption }

// ========== UPDATE PROVIDERS ==========

Expand Down
1 change: 1 addition & 0 deletions lib/screens/album_detail_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class _AlbumDetailScreenState extends ConsumerState<AlbumDetailScreen> {
floatingActionButton: _isSelectionMode
? null
: FloatingActionButton.extended(
elevation: 0,
onPressed: _showAddFilesSheet,
backgroundColor: context.accentColor,
icon: const Icon(Icons.add, color: Colors.white),
Expand Down
1 change: 1 addition & 0 deletions lib/screens/albums_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class _AlbumsScreenState extends ConsumerState<AlbumsScreen> {
data: (albums) => _buildAlbumsList(albums),
),
floatingActionButton: FloatingActionButton.extended(
elevation: 0,
onPressed: _showCreateAlbumDialog,
backgroundColor: context.accentColor,
icon: const Icon(Icons.add, color: Colors.white),
Expand Down
Loading
Loading