#130 motion photo auto play option
This commit is contained in:
parent
e4cfd82f34
commit
929b662d2a
12 changed files with 49 additions and 14 deletions
|
@ -6,12 +6,19 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Added
|
||||
|
||||
- Collection / Albums / Countries / Tags: list view (scalable like the grid view)
|
||||
- moving, editing or deleting multiple items can be cancelled
|
||||
- Viewer: option to auto play motion photos (after a small delay to show first the high-res photo)
|
||||
|
||||
### Changed
|
||||
|
||||
- upgraded Flutter to stable v2.8.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Collection: more consistent scroll bar thumb position to match the viewport
|
||||
- Settings: fixed file selection to import settings on older devices
|
||||
|
||||
## <a id="v1.5.7"></a>[v1.5.7] - 2021-12-01
|
||||
|
||||
### Added
|
||||
|
|
|
@ -565,6 +565,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
throw Exception("unsupported Android version")
|
||||
}
|
||||
|
||||
Log.d(LOG_TAG, "rename content at uri=$mediaUri")
|
||||
val uri = StorageUtils.getMediaStoreScopedStorageSafeUri(mediaUri, mimeType)
|
||||
|
||||
// `IS_PENDING` is necessary for `TITLE`, not for `DISPLAY_NAME`
|
||||
|
|
|
@ -816,6 +816,8 @@
|
|||
"@settingsViewerUseCutout": {},
|
||||
"settingsViewerMaximumBrightness": "Maximum brightness",
|
||||
"@settingsViewerMaximumBrightness": {},
|
||||
"settingsMotionPhotoAutoPlay": "Auto play motion photos",
|
||||
"@settingsMotionPhotoAutoPlay": {},
|
||||
"settingsImageBackground": "Image background",
|
||||
"@settingsImageBackground": {},
|
||||
|
||||
|
|
|
@ -381,6 +381,7 @@
|
|||
"settingsSectionViewer": "Visionneuse",
|
||||
"settingsViewerUseCutout": "Utiliser la zone d’encoche",
|
||||
"settingsViewerMaximumBrightness": "Luminosité maximale",
|
||||
"settingsMotionPhotoAutoPlay": "Lecture automatique des photos animées",
|
||||
"settingsImageBackground": "Arrière-plan de l’image",
|
||||
|
||||
"settingsViewerQuickActionsTile": "Actions rapides",
|
||||
|
|
|
@ -381,6 +381,7 @@
|
|||
"settingsSectionViewer": "뷰어",
|
||||
"settingsViewerUseCutout": "컷아웃 영역 사용",
|
||||
"settingsViewerMaximumBrightness": "최대 밝기",
|
||||
"settingsMotionPhotoAutoPlay": "모션 포토 자동 재생",
|
||||
"settingsImageBackground": "이미지 배경",
|
||||
|
||||
"settingsViewerQuickActionsTile": "빠른 작업",
|
||||
|
|
|
@ -67,6 +67,7 @@ class SettingsDefaults {
|
|||
static const enableOverlayBlurEffect = true; // `enableOverlayBlurEffect` has a contextual default value
|
||||
static const viewerUseCutout = true;
|
||||
static const viewerMaxBrightness = false;
|
||||
static const enableMotionPhotoAutoPlay = false;
|
||||
|
||||
// video
|
||||
static const videoQuickActions = [
|
||||
|
|
|
@ -83,6 +83,8 @@ class Settings extends ChangeNotifier {
|
|||
static const enableOverlayBlurEffectKey = 'enable_overlay_blur_effect';
|
||||
static const viewerUseCutoutKey = 'viewer_use_cutout';
|
||||
static const viewerMaxBrightnessKey = 'viewer_max_brightness';
|
||||
static const enableMotionPhotoAutoPlayKey = 'motion_photo_auto_play';
|
||||
static const imageBackgroundKey = 'image_background';
|
||||
|
||||
// video
|
||||
static const videoQuickActionsKey = 'video_quick_actions';
|
||||
|
@ -104,9 +106,6 @@ class Settings extends ChangeNotifier {
|
|||
static const coordinateFormatKey = 'coordinates_format';
|
||||
static const unitSystemKey = 'unit_system';
|
||||
|
||||
// rendering
|
||||
static const imageBackgroundKey = 'image_background';
|
||||
|
||||
// search
|
||||
static const saveSearchHistoryKey = 'save_search_history';
|
||||
static const searchHistoryKey = 'search_history';
|
||||
|
@ -365,6 +364,14 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set viewerMaxBrightness(bool newValue) => setAndNotify(viewerMaxBrightnessKey, newValue);
|
||||
|
||||
bool get enableMotionPhotoAutoPlay => getBoolOrDefault(enableMotionPhotoAutoPlayKey, SettingsDefaults.enableMotionPhotoAutoPlay);
|
||||
|
||||
set enableMotionPhotoAutoPlay(bool newValue) => setAndNotify(enableMotionPhotoAutoPlayKey, newValue);
|
||||
|
||||
EntryBackground get imageBackground => getEnumOrDefault(imageBackgroundKey, SettingsDefaults.imageBackground, EntryBackground.values);
|
||||
|
||||
set imageBackground(EntryBackground newValue) => setAndNotify(imageBackgroundKey, newValue.toString());
|
||||
|
||||
// video
|
||||
|
||||
List<VideoAction> get videoQuickActions => getEnumListOrDefault(videoQuickActionsKey, SettingsDefaults.videoQuickActions, VideoAction.values);
|
||||
|
@ -427,12 +434,6 @@ class Settings extends ChangeNotifier {
|
|||
|
||||
set unitSystem(UnitSystem newValue) => setAndNotify(unitSystemKey, newValue.toString());
|
||||
|
||||
// rendering
|
||||
|
||||
EntryBackground get imageBackground => getEnumOrDefault(imageBackgroundKey, SettingsDefaults.imageBackground, EntryBackground.values);
|
||||
|
||||
set imageBackground(EntryBackground newValue) => setAndNotify(imageBackgroundKey, newValue.toString());
|
||||
|
||||
// search
|
||||
|
||||
bool get saveSearchHistory => getBoolOrDefault(saveSearchHistoryKey, SettingsDefaults.saveSearchHistory);
|
||||
|
@ -613,6 +614,7 @@ class Settings extends ChangeNotifier {
|
|||
case enableOverlayBlurEffectKey:
|
||||
case viewerUseCutoutKey:
|
||||
case viewerMaxBrightnessKey:
|
||||
case enableMotionPhotoAutoPlayKey:
|
||||
case enableVideoHardwareAccelerationKey:
|
||||
case enableVideoAutoPlayKey:
|
||||
case subtitleShowOutlineKey:
|
||||
|
@ -633,12 +635,12 @@ class Settings extends ChangeNotifier {
|
|||
case albumSortFactorKey:
|
||||
case countrySortFactorKey:
|
||||
case tagSortFactorKey:
|
||||
case imageBackgroundKey:
|
||||
case videoLoopModeKey:
|
||||
case subtitleTextAlignmentKey:
|
||||
case infoMapStyleKey:
|
||||
case coordinateFormatKey:
|
||||
case unitSystemKey:
|
||||
case imageBackgroundKey:
|
||||
case accessibilityAnimationsKey:
|
||||
case timeToTakeActionKey:
|
||||
if (value is String) {
|
||||
|
|
|
@ -59,6 +59,7 @@ class Durations {
|
|||
static const collectionScrollMonitoringTimerDelay = Duration(milliseconds: 100);
|
||||
static const highlightJumpDelay = Duration(milliseconds: 400);
|
||||
static const highlightScrollInitDelay = Duration(milliseconds: 800);
|
||||
static const motionPhotoAutoPlayDelay = Duration(milliseconds: 700);
|
||||
static const videoOverlayHideDelay = Duration(milliseconds: 500);
|
||||
static const videoProgressTimerInterval = Duration(milliseconds: 300);
|
||||
static const doubleBackTimerDelay = Duration(milliseconds: 1000);
|
||||
|
|
|
@ -42,6 +42,14 @@ class ViewerSection extends StatelessWidget {
|
|||
title: Text(context.l10n.settingsViewerMaximumBrightness),
|
||||
),
|
||||
),
|
||||
Selector<Settings, bool>(
|
||||
selector: (context, s) => s.enableMotionPhotoAutoPlay,
|
||||
builder: (context, current, child) => SwitchListTile(
|
||||
value: current,
|
||||
onChanged: (v) => settings.enableMotionPhotoAutoPlay = v,
|
||||
title: Text(context.l10n.settingsMotionPhotoAutoPlay),
|
||||
),
|
||||
),
|
||||
Selector<Settings, EntryBackground>(
|
||||
selector: (context, s) => s.imageBackground,
|
||||
builder: (context, current, child) => ListTile(
|
||||
|
|
|
@ -630,7 +630,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
|||
// auto play/pause when changing page
|
||||
Future<void> _onPageChange() async {
|
||||
await _pauseVideoControllers();
|
||||
if (settings.enableVideoAutoPlay) {
|
||||
if (settings.enableVideoAutoPlay || (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay)) {
|
||||
final page = multiPageController.page;
|
||||
final pageInfo = multiPageInfo.getByIndex(page)!;
|
||||
if (pageInfo.isVideo) {
|
||||
|
@ -644,6 +644,13 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
|||
_multiPageControllerPageListeners[multiPageController] = _onPageChange;
|
||||
multiPageController.pageNotifier.addListener(_onPageChange);
|
||||
await _onPageChange();
|
||||
|
||||
if (entry.isMotionPhoto && settings.enableMotionPhotoAutoPlay) {
|
||||
await Future.delayed(Durations.motionPhotoAutoPlayDelay);
|
||||
if (entry == _entryNotifier.value) {
|
||||
multiPageController.page = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,9 @@ class _EntryPageViewState extends State<EntryPageView> {
|
|||
|
||||
AvesEntry get entry => widget.pageEntry;
|
||||
|
||||
// use the high res photo as cover for the video part of a motion photo
|
||||
ImageProvider get videoCoverUriImage => mainEntry.isMotionPhoto ? mainEntry.uriImage : entry.uriImage;
|
||||
|
||||
static const initialScale = ScaleLevel(ref: ScaleReference.contained);
|
||||
static const minScale = ScaleLevel(ref: ScaleReference.contained);
|
||||
static const maxScale = ScaleLevel(factor: 2.0);
|
||||
|
@ -98,7 +101,7 @@ class _EntryPageViewState extends State<EntryPageView> {
|
|||
_subscriptions.add(_magnifierController.scaleBoundariesStream.listen(_onViewScaleBoundariesChanged));
|
||||
if (entry.isVideo) {
|
||||
_videoCoverStreamListener = ImageStreamListener((image, _) => _videoCoverInfoNotifier.value = image);
|
||||
_videoCoverStream = entry.uriImage.resolve(ImageConfiguration.empty);
|
||||
_videoCoverStream = videoCoverUriImage.resolve(ImageConfiguration.empty);
|
||||
_videoCoverStream!.addListener(_videoCoverStreamListener);
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +251,7 @@ class _EntryPageViewState extends State<EntryPageView> {
|
|||
controller: coverController,
|
||||
displaySize: coverSize,
|
||||
child: Image(
|
||||
image: entry.uriImage,
|
||||
image: videoCoverUriImage,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"menuActionConfigureView",
|
||||
"viewDialogTabLayout",
|
||||
"tileLayoutGrid",
|
||||
"tileLayoutList"
|
||||
"tileLayoutList",
|
||||
"settingsMotionPhotoAutoPlay"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue