diff --git a/i18n/en.json b/i18n/en.json index fd6c3c501..dde028d0e 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2204,6 +2204,8 @@ "theme": "Theme", "theme_selection": "Theme selection", "theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference", + "theme_setting_asset_list_smooth_scrubbing_subtitle": "Scroll continuously when dragging the scrollbar instead of snapping to months", + "theme_setting_asset_list_smooth_scrubbing_title": "Smooth timeline scrubbing", "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({count})", "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", diff --git a/mobile/lib/domain/models/config/app_config.dart b/mobile/lib/domain/models/config/app_config.dart index bc089e621..bf55b407e 100644 --- a/mobile/lib/domain/models/config/app_config.dart +++ b/mobile/lib/domain/models/config/app_config.dart @@ -75,6 +75,7 @@ abstract class AppConfig with _$AppConfig { .timelineTilesPerRow => timeline.tilesPerRow, .timelineGroupAssetsBy => timeline.groupAssetsBy, .timelineStorageIndicator => timeline.storageIndicator, + .timelineSmoothScrubbing => timeline.smoothScrubbing, .mapShowFavoriteOnly => map.favoritesOnly, .mapRelativeDate => map.relativeDays, .mapCustomFrom => map.customFrom, @@ -131,6 +132,7 @@ abstract class AppConfig with _$AppConfig { .timelineTilesPerRow => copyWith(timeline: timeline.copyWith(tilesPerRow: value as int)), .timelineGroupAssetsBy => copyWith(timeline: timeline.copyWith(groupAssetsBy: value as GroupAssetsBy)), .timelineStorageIndicator => copyWith(timeline: timeline.copyWith(storageIndicator: value as bool)), + .timelineSmoothScrubbing => copyWith(timeline: timeline.copyWith(smoothScrubbing: value as bool)), .mapShowFavoriteOnly => copyWith(map: map.copyWith(favoritesOnly: value as bool)), .mapRelativeDate => copyWith(map: map.copyWith(relativeDays: value as int)), .mapCustomFrom => copyWith(map: map.copyWith(customFrom: .fromNullable(value as DateTime?))), diff --git a/mobile/lib/domain/models/config/timeline_config.dart b/mobile/lib/domain/models/config/timeline_config.dart index c4681b0a6..a949a112d 100644 --- a/mobile/lib/domain/models/config/timeline_config.dart +++ b/mobile/lib/domain/models/config/timeline_config.dart @@ -10,5 +10,6 @@ abstract class TimelineConfig with _$TimelineConfig { @Default(4) int tilesPerRow, @Default(GroupAssetsBy.day) GroupAssetsBy groupAssetsBy, @Default(true) bool storageIndicator, + @Default(false) bool smoothScrubbing, }) = _TimelineConfig; } diff --git a/mobile/lib/domain/models/settings_key.dart b/mobile/lib/domain/models/settings_key.dart index 85e58ffcf..ab0ca06ec 100644 --- a/mobile/lib/domain/models/settings_key.dart +++ b/mobile/lib/domain/models/settings_key.dart @@ -48,6 +48,7 @@ enum SettingsKey { timelineTilesPerRow(), timelineGroupAssetsBy(codec: EnumCodec(GroupAssetsBy.values)), timelineStorageIndicator(), + timelineSmoothScrubbing(), // Log logLevel(codec: EnumCodec(LogLevel.values)), diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 78e0c1d1b..b3ff9149f 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -328,6 +328,8 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi final isMultiSelectStatusVisible = !isSelectionMode && isMultiSelectEnabled; final isBottomWidgetVisible = widget.bottomSheet != null && (isMultiSelectStatusVisible || widget.persistentBottomBar); + final smoothScrubbing = ref.watch(appConfigProvider.select((config) => config.timeline.smoothScrubbing)); + final snapToMonth = widget.snapToMonth && !smoothScrubbing; return PopScope( canPop: !isMultiSelectEnabled, @@ -409,7 +411,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi final Widget timeline; if (widget.withScrubber) { timeline = Scrubber( - snapToMonth: widget.snapToMonth, + snapToMonth: snapToMonth, layoutSegments: segments, timelineHeight: maxHeight, topPadding: topPadding, diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart index de32179c3..1de8ba667 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart @@ -16,6 +16,7 @@ class AssetListSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final storageIndicator = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator))); + final smoothScrubbing = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.smoothScrubbing))); final assetListSetting = [ SettingsSwitchListTile( @@ -27,6 +28,16 @@ class AssetListSettings extends HookConsumerWidget { ref.invalidate(settingsProvider); }, ), + SettingsSwitchListTile( + valueNotifier: smoothScrubbing, + title: context.t.theme_setting_asset_list_smooth_scrubbing_title, + subtitle: context.t.theme_setting_asset_list_smooth_scrubbing_subtitle, + onChanged: (value) { + unawaited(ref.read(settingsProvider).write(.timelineSmoothScrubbing, value)); + ref.invalidate(appSettingsServiceProvider); + ref.invalidate(settingsProvider); + }, + ), const LayoutSettings(), const GroupSettings(), ];