Polish media viewer, pull-to-sync, search nav, and add seek bar presets

- Remove peek details sheet from media viewer; scope Activity tab to
  the current file via client-side filtering instead of unreliable
  server-side query params
- Add file rename action (WebDAV MOVE); fix search result taps to
  switch to Files tab and rebuild breadcrumbs from an absolute path
- Add swipe-to-delete confirmation dialog; fix a crash from disposing
  the rename dialog's TextEditingController too early
- Rework pull-to-sync gesture so sync and lock-open thresholds fire
  independently within one gesture, raise thresholds, and add a
  classic Material refresh spinner
- Add "tap active tab to scroll to top" behavior with a settings toggle
- Align selection toolbar padding with the sort/filter controls row
- Replace app icon assets (adaptive icon + splash screen) with
  updated IconKitchen export
- Replace the seek bar style dropdown with a Default/Wavy/Slim/Squiggly
  preset picker, each rendered with a live, perpetually-animated
  preview via a new shared SeekBarPainter/SeekBarPreview

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 10:25:52 -04:00
co-authored by Claude Sonnet 5
parent 1e42fcc0e6
commit d5675d853c
37 changed files with 951 additions and 376 deletions
+64 -4
View File
@@ -49,10 +49,12 @@ class _CachedDirectory {
const _CachedDirectory(this.items, this.fetchedAt);
}
/// The two linear progress indicator styles Material 3 offers for the video
/// player's seek bar: an animated travelling-wave line (matching Android's
/// current media player), or a plain flat classic bar.
enum MediaProgressBarStyle { wavy, classic }
/// Thumb/track presets for the video player's seek bar, matching the four
/// combinations offered by other Material You media players: a Material 3
/// slider-style thumb ([classic]), an animated travelling wave with a round
/// thumb ([wavy]), a thin flat bar with no distinct thumb ([slim]), and an
/// animated wave with a tick-mark thumb ([squiggly]).
enum MediaProgressBarStyle { classic, wavy, slim, squiggly }
class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
static const _storage = FlutterSecureStorage();
@@ -83,6 +85,7 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
static const _prefMediaProgressBarStyle = 'ui_media_progress_bar_style';
static const _prefCachePolicy = 'ui_cache_policy';
static const _prefCacheIntervalMinutes = 'ui_cache_interval_minutes';
static const _prefTapTabToScrollTop = 'ui_tap_tab_to_scroll_top';
final Future<SharedPreferences> _prefsFuture =
SharedPreferences.getInstance();
@@ -111,9 +114,15 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
// UI settings
double _bottomBarOpacity = 0.55;
double _bottomBarBlur = 28;
bool _tapTabToScrollTop = true;
// Navigation state
String _currentFolderPath = '/';
// A one-shot request for the shell to switch its active bottom-nav tab
// (e.g. a search result landing on Files) - consumed and cleared by
// MainShellView the next time it builds, not a persisted preference.
AppTab? _requestedTab;
List<String> _pathStack = ['/'];
bool _isGridView = false;
bool _showFavoritesOnlyFiles = false;
@@ -214,8 +223,24 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
MediaProgressBarStyle get mediaProgressBarStyle => _mediaProgressBarStyle;
double get bottomBarOpacity => _bottomBarOpacity;
double get bottomBarBlur => _bottomBarBlur;
bool get tapTabToScrollTop => _tapTabToScrollTop;
String get currentFolderPath => _currentFolderPath;
AppTab? get requestedTab => _requestedTab;
/// Asks the shell to switch its active bottom-nav tab to [tab] - e.g. so
/// tapping a search result lands the user on the Files tab even if they
/// opened search from somewhere else.
void requestTab(AppTab tab) {
_requestedTab = tab;
notifyListeners();
}
/// Called by the shell once it's consumed [requestedTab].
void consumeRequestedTab() {
_requestedTab = null;
}
List<String> get pathStack => _pathStack;
bool get isGridView => _isGridView;
bool get showFavoritesOnlyFiles => _showFavoritesOnlyFiles;
@@ -382,6 +407,8 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
_bottomBarOpacity =
prefs.getDouble(_prefBottomBarOpacity) ?? _bottomBarOpacity;
_bottomBarBlur = prefs.getDouble(_prefBottomBarBlur) ?? _bottomBarBlur;
_tapTabToScrollTop =
prefs.getBool(_prefTapTabToScrollTop) ?? _tapTabToScrollTop;
_isGridView = prefs.getBool(_prefGridView) ?? _isGridView;
_showFavoritesOnlyFiles =
prefs.getBool(_prefShowFavoritesOnlyFiles) ?? _showFavoritesOnlyFiles;
@@ -761,6 +788,24 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
await _navigateTo(_pathStack.last);
}
/// Navigates directly to an arbitrary absolute folder path (e.g. from a
/// search result) - unlike [navigateToFolder], which assumes [path] is a
/// child of wherever the user is currently browsing and just appends it,
/// this rebuilds the whole breadcrumb trail from root so it's correct
/// regardless of where the user was before.
Future<void> navigateToAbsoluteFolder(String path) async {
final normalized = path.trim().replaceAll(RegExp(r'/+$'), '');
final segments = normalized.split('/').where((s) => s.isNotEmpty).toList();
final stack = <String>['/'];
var current = '';
for (final segment in segments) {
current = '$current/$segment';
stack.add(current);
}
_pathStack = stack;
await _navigateTo(stack.last);
}
/// True if [path]'s cached listing (if any) is still usable under the
/// current [CachePolicy] - never for [CachePolicy.never], indefinitely
/// for [CachePolicy.manual], and until it's older than
@@ -970,6 +1015,15 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
return success;
}
Future<bool> renameItem(NextcloudItem item, String newName) async {
if (_service == null) return false;
final success = await _service!.renameItem(item.path, newName);
if (success) {
await refreshData();
}
return success;
}
Future<void> fetchTrash() async {
if (!_isLoggedIn || _service == null) return;
@@ -1239,6 +1293,12 @@ class ServerProvider extends ChangeNotifier with WidgetsBindingObserver {
_prefsFuture.then((p) => p.setDouble(_prefBottomBarBlur, value));
}
void setTapTabToScrollTop(bool value) {
_tapTabToScrollTop = value;
notifyListeners();
_prefsFuture.then((p) => p.setBool(_prefTapTabToScrollTop, value));
}
void setThemeMode(ThemeMode mode) {
_themeMode = mode;
notifyListeners();