mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
Support playing live photos taken with a Google Pixel
This commit is contained in:
parent
4dc2530c01
commit
654f6c0a43
14 changed files with 425 additions and 24 deletions
BIN
app/assets/2.0x/ic_motion_photos_play_24dp.png
Normal file
BIN
app/assets/2.0x/ic_motion_photos_play_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
app/assets/3.0x/ic_motion_photos_play_24dp.png
Normal file
BIN
app/assets/3.0x/ic_motion_photos_play_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
app/assets/ic_motion_photos_play_24dp.png
Normal file
BIN
app/assets/ic_motion_photos_play_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 794 B |
|
@ -1,2 +1,3 @@
|
||||||
const icAddCollectionsOutlined24 =
|
const icAddCollectionsOutlined24 =
|
||||||
"assets/ic_add_collections_outlined_24dp.png";
|
"assets/ic_add_collections_outlined_24dp.png";
|
||||||
|
const icMotionPhotosPlay24dp = "assets/ic_motion_photos_play_24dp.png";
|
||||||
|
|
13
app/lib/live_photo_util.dart
Normal file
13
app/lib/live_photo_util.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
|
||||||
|
|
||||||
|
LivePhotoType? getLivePhotoTypeFromFile(FileDescriptor file) {
|
||||||
|
final filenameL = file.filename.toLowerCase();
|
||||||
|
if (filenameL.startsWith("pxl_") && filenameL.endsWith(".mp.jpg")) {
|
||||||
|
return LivePhotoType.googleMp;
|
||||||
|
} else if (filenameL.startsWith("mvimg_") && filenameL.endsWith(".jpg")) {
|
||||||
|
return LivePhotoType.googleMvimg;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ class HorizontalPageViewer extends StatefulWidget {
|
||||||
final HorizontalPageViewerController controller;
|
final HorizontalPageViewerController controller;
|
||||||
final double viewportFraction;
|
final double viewportFraction;
|
||||||
final bool canSwitchPage;
|
final bool canSwitchPage;
|
||||||
final ValueChanged<int>? onPageChanged;
|
final void Function(int from, int to)? onPageChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HorizontalPageViewerState extends State<HorizontalPageViewer> {
|
class _HorizontalPageViewerState extends State<HorizontalPageViewer> {
|
||||||
|
|
290
app/lib/widget/live_photo_viewer.dart
Normal file
290
app/lib/widget/live_photo_viewer.dart
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
|
import 'package:nc_photos/cache_manager_util.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:nc_photos/use_case/request_public_link.dart';
|
||||||
|
import 'package:nc_photos/widget/cached_network_image_mod.dart' as mod;
|
||||||
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
|
||||||
|
|
||||||
|
part 'live_photo_viewer.g.dart';
|
||||||
|
|
||||||
|
class LivePhotoViewer extends StatefulWidget {
|
||||||
|
const LivePhotoViewer({
|
||||||
|
super.key,
|
||||||
|
required this.account,
|
||||||
|
required this.file,
|
||||||
|
this.onLoaded,
|
||||||
|
this.onLoadFailure,
|
||||||
|
this.onHeightChanged,
|
||||||
|
this.canPlay = true,
|
||||||
|
this.livePhotoType,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _LivePhotoViewerState();
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
final FileDescriptor file;
|
||||||
|
final VoidCallback? onLoaded;
|
||||||
|
final VoidCallback? onLoadFailure;
|
||||||
|
final ValueChanged<double>? onHeightChanged;
|
||||||
|
final bool canPlay;
|
||||||
|
final LivePhotoType? livePhotoType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _LivePhotoViewerState extends State<LivePhotoViewer> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getVideoUrl().then((url) {
|
||||||
|
if (mounted) {
|
||||||
|
_initController(url);
|
||||||
|
}
|
||||||
|
}).onError((e, stacktrace) {
|
||||||
|
_log.shout("[initState] Failed while _getVideoUrl", e, stacktrace);
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(exception_util.toUserString(e)),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
widget.onLoadFailure?.call();
|
||||||
|
});
|
||||||
|
|
||||||
|
_lifecycleListener = AppLifecycleListener(onShow: () {
|
||||||
|
if (_controller.value.isInitialized) {
|
||||||
|
_controller.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_lifecycleListener.dispose();
|
||||||
|
_controller.removeListener(_onControllerChanged);
|
||||||
|
_controllerValue?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget content;
|
||||||
|
if (_isControllerInitialized && _controller.value.isInitialized) {
|
||||||
|
content = _buildPlayer(context);
|
||||||
|
} else {
|
||||||
|
content = _PlaceHolderView(
|
||||||
|
account: widget.account,
|
||||||
|
file: widget.file,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: content,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initController(String url) async {
|
||||||
|
try {
|
||||||
|
_controllerValue = VideoPlayerController.networkUrl(
|
||||||
|
Uri.parse(url),
|
||||||
|
httpHeaders: {
|
||||||
|
"Authorization": AuthUtil.fromAccount(widget.account).toHeaderValue(),
|
||||||
|
},
|
||||||
|
livePhotoType: widget.livePhotoType,
|
||||||
|
);
|
||||||
|
await _controller.initialize();
|
||||||
|
await _controller.setVolume(0);
|
||||||
|
await _controller.setLooping(true);
|
||||||
|
widget.onLoaded?.call();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (_key.currentContext != null) {
|
||||||
|
widget.onHeightChanged?.call(_key.currentContext!.size!.height);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_controller.addListener(_onControllerChanged);
|
||||||
|
setState(() {
|
||||||
|
_isControllerInitialized = true;
|
||||||
|
});
|
||||||
|
await _controller.play();
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.shout("[_initController] Failed while initialize", e, stackTrace);
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(exception_util.toUserString(e)),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
widget.onLoadFailure?.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPlayer(BuildContext context) {
|
||||||
|
if (_controller.value.isPlaying && !widget.canPlay) {
|
||||||
|
_log.info("Pause playback");
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_controller.pause();
|
||||||
|
});
|
||||||
|
} else if (!_controller.value.isPlaying && widget.canPlay) {
|
||||||
|
_log.info("Resume playback");
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_controller.play();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Center(
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: AspectRatio(
|
||||||
|
key: _key,
|
||||||
|
aspectRatio: _controller.value.aspectRatio,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: VideoPlayer(_controller),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!_isLoaded) ...[
|
||||||
|
_PlaceHolderView(
|
||||||
|
account: widget.account,
|
||||||
|
file: widget.file,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _getVideoUrl() async {
|
||||||
|
if (getRawPlatform() == NpPlatform.web) {
|
||||||
|
return RequestPublicLink()(widget.account, widget.file);
|
||||||
|
} else {
|
||||||
|
return api_util.getFileUrl(widget.account, widget.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onControllerChanged() {
|
||||||
|
if (!_controller.value.isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_controller.value.isPlaying != _isPlaying) {
|
||||||
|
setState(() {
|
||||||
|
_isPlaying = !_isPlaying;
|
||||||
|
_isLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoPlayerController get _controller => _controllerValue!;
|
||||||
|
|
||||||
|
final _key = GlobalKey();
|
||||||
|
bool _isControllerInitialized = false;
|
||||||
|
VideoPlayerController? _controllerValue;
|
||||||
|
var _isPlaying = false;
|
||||||
|
var _isLoaded = false;
|
||||||
|
late final AppLifecycleListener _lifecycleListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaceHolderView extends StatelessWidget {
|
||||||
|
const _PlaceHolderView({
|
||||||
|
required this.account,
|
||||||
|
required this.file,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
mod.CachedNetworkImage(
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
cacheManager: LargeImageCacheManager.inst,
|
||||||
|
imageUrl: api_util.getFilePreviewUrl(
|
||||||
|
account,
|
||||||
|
file,
|
||||||
|
width: k.photoLargeSize,
|
||||||
|
height: k.photoLargeSize,
|
||||||
|
isKeepAspectRatio: true,
|
||||||
|
),
|
||||||
|
httpHeaders: {
|
||||||
|
"Authorization": AuthUtil.fromAccount(account).toHeaderValue(),
|
||||||
|
},
|
||||||
|
fadeInDuration: const Duration(),
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||||
|
imageBuilder: (context, child, imageProvider) {
|
||||||
|
const SizeChangedLayoutNotification().dispatch(context);
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ColoredBox(color: Colors.black.withOpacity(.7)),
|
||||||
|
const Center(child: _ProgressIndicator()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
final FileDescriptor file;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProgressIndicator extends StatefulWidget {
|
||||||
|
const _ProgressIndicator();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _ProgressIndicatorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProgressIndicatorState extends State<_ProgressIndicator>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
animationController.repeat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
animationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
RotationTransition(
|
||||||
|
turns: animationController
|
||||||
|
.drive(CurveTween(curve: Curves.easeInOutCubic)),
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
child: Icon(
|
||||||
|
Icons.motion_photos_on_outlined,
|
||||||
|
size: 64,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.play_arrow_rounded,
|
||||||
|
size: 48,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
late final animationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(seconds: 1),
|
||||||
|
);
|
||||||
|
}
|
14
app/lib/widget/live_photo_viewer.g.dart
Normal file
14
app/lib/widget/live_photo_viewer.g.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'live_photo_viewer.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// NpLogGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_LivePhotoViewerStateNpLog on _LivePhotoViewerState {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger("widget.live_photo_viewer._LivePhotoViewerState");
|
||||||
|
}
|
|
@ -10,13 +10,13 @@ class PageChangedListener {
|
||||||
if (pageController.hasClients) {
|
if (pageController.hasClients) {
|
||||||
final page = pageController.page!.round();
|
final page = pageController.page!.round();
|
||||||
if (page != _prevPage) {
|
if (page != _prevPage) {
|
||||||
onPageChanged?.call(page);
|
onPageChanged?.call(_prevPage, page);
|
||||||
_prevPage = page;
|
_prevPage = page;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final PageController pageController;
|
final PageController pageController;
|
||||||
final ValueChanged<int>? onPageChanged;
|
final void Function(int from, int to)? onPageChanged;
|
||||||
int _prevPage;
|
int _prevPage;
|
||||||
}
|
}
|
||||||
|
|
17
app/lib/widget/png_icon.dart
Normal file
17
app/lib/widget/png_icon.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PngIcon extends StatelessWidget {
|
||||||
|
const PngIcon(
|
||||||
|
this.asset, {
|
||||||
|
super.key,
|
||||||
|
this.size = 24,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Image.asset(asset, width: size, height: size);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String asset;
|
||||||
|
final double size;
|
||||||
|
}
|
|
@ -110,8 +110,8 @@ class _VideoViewerState extends State<VideoViewer>
|
||||||
|
|
||||||
Future<void> _initController(String url) async {
|
Future<void> _initController(String url) async {
|
||||||
try {
|
try {
|
||||||
_controllerValue = VideoPlayerController.network(
|
_controllerValue = VideoPlayerController.networkUrl(
|
||||||
url,
|
Uri.parse(url),
|
||||||
httpHeaders: {
|
httpHeaders: {
|
||||||
"Authorization": AuthUtil.fromAccount(widget.account).toHeaderValue(),
|
"Authorization": AuthUtil.fromAccount(widget.account).toHeaderValue(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,7 @@ import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/asset.dart';
|
||||||
import 'package:nc_photos/controller/account_controller.dart';
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
import 'package:nc_photos/controller/collection_items_controller.dart';
|
import 'package:nc_photos/controller/collection_items_controller.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
@ -23,6 +24,7 @@ import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/flutter_util.dart';
|
import 'package:nc_photos/flutter_util.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/live_photo_util.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/platform/features.dart' as features;
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/share_handler.dart';
|
import 'package:nc_photos/share_handler.dart';
|
||||||
|
@ -34,6 +36,8 @@ import 'package:nc_photos/widget/horizontal_page_viewer.dart';
|
||||||
import 'package:nc_photos/widget/image_editor.dart';
|
import 'package:nc_photos/widget/image_editor.dart';
|
||||||
import 'package:nc_photos/widget/image_enhancer.dart';
|
import 'package:nc_photos/widget/image_enhancer.dart';
|
||||||
import 'package:nc_photos/widget/image_viewer.dart';
|
import 'package:nc_photos/widget/image_viewer.dart';
|
||||||
|
import 'package:nc_photos/widget/live_photo_viewer.dart';
|
||||||
|
import 'package:nc_photos/widget/png_icon.dart';
|
||||||
import 'package:nc_photos/widget/slideshow_dialog.dart';
|
import 'package:nc_photos/widget/slideshow_dialog.dart';
|
||||||
import 'package:nc_photos/widget/slideshow_viewer.dart';
|
import 'package:nc_photos/widget/slideshow_viewer.dart';
|
||||||
import 'package:nc_photos/widget/video_viewer.dart';
|
import 'package:nc_photos/widget/video_viewer.dart';
|
||||||
|
@ -43,6 +47,7 @@ import 'package:nc_photos/widget/viewer_mixin.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
|
||||||
|
|
||||||
part 'viewer.g.dart';
|
part 'viewer.g.dart';
|
||||||
|
|
||||||
|
@ -164,8 +169,10 @@ class _ViewerState extends State<Viewer>
|
||||||
controller: _viewerController,
|
controller: _viewerController,
|
||||||
viewportFraction: _viewportFraction,
|
viewportFraction: _viewportFraction,
|
||||||
canSwitchPage: _canSwitchPage(),
|
canSwitchPage: _canSwitchPage(),
|
||||||
onPageChanged: (_) {
|
onPageChanged: (from, to) {
|
||||||
setState(() {});
|
setState(() {
|
||||||
|
_pageStates[from]?.shouldPlayLivePhoto = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_isShowAppBar)
|
if (_isShowAppBar)
|
||||||
|
@ -206,6 +213,17 @@ class _ViewerState extends State<Viewer>
|
||||||
centerTitle: isCentered,
|
centerTitle: isCentered,
|
||||||
actions: [
|
actions: [
|
||||||
if (!_isDetailPaneActive && _canOpenDetailPane()) ...[
|
if (!_isDetailPaneActive && _canOpenDetailPane()) ...[
|
||||||
|
if (getLivePhotoTypeFromFile(file) != null)
|
||||||
|
if (_pageStates[index]?.shouldPlayLivePhoto ?? false)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.motion_photos_pause_outlined),
|
||||||
|
onPressed: () => _onPauseMotionPhotosPressed(index),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
IconButton(
|
||||||
|
icon: const PngIcon(icMotionPhotosPlay24dp),
|
||||||
|
onPressed: () => _onPlayMotionPhotosPressed(index),
|
||||||
|
),
|
||||||
(_pageStates[index]?.favoriteOverride ?? file.fdIsFavorite) == true
|
(_pageStates[index]?.favoriteOverride ?? file.fdIsFavorite) == true
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.star),
|
icon: const Icon(Icons.star),
|
||||||
|
@ -361,6 +379,15 @@ class _ViewerState extends State<Viewer>
|
||||||
Widget _buildItemView(BuildContext context, int index) {
|
Widget _buildItemView(BuildContext context, int index) {
|
||||||
final file = _streamFilesView[index];
|
final file = _streamFilesView[index];
|
||||||
if (file_util.isSupportedImageFormat(file)) {
|
if (file_util.isSupportedImageFormat(file)) {
|
||||||
|
final shouldPlayLivePhoto = _pageStates[index]!.shouldPlayLivePhoto;
|
||||||
|
if (shouldPlayLivePhoto) {
|
||||||
|
final livePhotoType = getLivePhotoTypeFromFile(file);
|
||||||
|
if (livePhotoType != null) {
|
||||||
|
return _buildLivePhotoView(context, index, livePhotoType);
|
||||||
|
} else {
|
||||||
|
_log.warning("[_buildItemView] Not a live photo");
|
||||||
|
}
|
||||||
|
}
|
||||||
return _buildImageView(context, index);
|
return _buildImageView(context, index);
|
||||||
} else if (file_util.isSupportedVideoFormat(file)) {
|
} else if (file_util.isSupportedVideoFormat(file)) {
|
||||||
return _buildVideoView(context, index);
|
return _buildVideoView(context, index);
|
||||||
|
@ -404,6 +431,25 @@ class _ViewerState extends State<Viewer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildLivePhotoView(
|
||||||
|
BuildContext context, int index, LivePhotoType livePhotoType) {
|
||||||
|
return LivePhotoViewer(
|
||||||
|
account: widget.account,
|
||||||
|
file: _streamFilesView[index],
|
||||||
|
onLoaded: () => _onVideoLoaded(index),
|
||||||
|
onHeightChanged: (height) => _updateItemHeight(index, height),
|
||||||
|
canPlay: !_isDetailPaneActive,
|
||||||
|
livePhotoType: livePhotoType,
|
||||||
|
onLoadFailure: () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_pageStates[index]!.shouldPlayLivePhoto = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool _onPageContentScrolled(ScrollNotification notification, int index) {
|
bool _onPageContentScrolled(ScrollNotification notification, int index) {
|
||||||
if (!_canOpenDetailPane()) {
|
if (!_canOpenDetailPane()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -538,6 +584,18 @@ class _ViewerState extends State<Viewer>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onPlayMotionPhotosPressed(int index) {
|
||||||
|
setState(() {
|
||||||
|
_pageStates[index]!.shouldPlayLivePhoto = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onPauseMotionPhotosPressed(int index) {
|
||||||
|
setState(() {
|
||||||
|
_pageStates[index]!.shouldPlayLivePhoto = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _onFavoritePressed(int index) async {
|
Future<void> _onFavoritePressed(int index) async {
|
||||||
if (_pageStates[index]!.isProcessingFavorite) {
|
if (_pageStates[index]!.isProcessingFavorite) {
|
||||||
_log.fine("[_onFavoritePressed] Process ongoing, ignored");
|
_log.fine("[_onFavoritePressed] Process ongoing, ignored");
|
||||||
|
@ -931,6 +989,7 @@ class _PageState {
|
||||||
|
|
||||||
bool isProcessingFavorite = false;
|
bool isProcessingFavorite = false;
|
||||||
bool? favoriteOverride;
|
bool? favoriteOverride;
|
||||||
|
bool shouldPlayLivePhoto = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppBarTitle extends StatelessWidget {
|
class _AppBarTitle extends StatelessWidget {
|
||||||
|
|
|
@ -1776,36 +1776,37 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "packages/video_player/video_player"
|
path: "packages/video_player/video_player"
|
||||||
ref: "video_player-v2.4.5-nc-photos-2"
|
ref: "video_player-v2.8.6-nc-photos-1"
|
||||||
resolved-ref: b5c28c21f29f09b623900d5a8cc88a70da29de3a
|
resolved-ref: ea754fd61b8bb3c431bd33d1a07709b6f501345c
|
||||||
url: "https://gitlab.com/nc-photos/flutter-plugins"
|
url: "https://gitlab.com/nc-photos/flutter-plugins"
|
||||||
source: git
|
source: git
|
||||||
version: "2.4.5"
|
version: "2.8.6"
|
||||||
video_player_android:
|
video_player_android:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
path: "packages/video_player/video_player_android"
|
path: "packages/video_player/video_player_android"
|
||||||
ref: "video_player-v2.4.5-nc-photos-2"
|
ref: "video_player-v2.8.6-nc-photos-1"
|
||||||
resolved-ref: b5c28c21f29f09b623900d5a8cc88a70da29de3a
|
resolved-ref: ea754fd61b8bb3c431bd33d1a07709b6f501345c
|
||||||
url: "https://gitlab.com/nc-photos/flutter-plugins"
|
url: "https://gitlab.com/nc-photos/flutter-plugins"
|
||||||
source: git
|
source: git
|
||||||
version: "2.3.6"
|
version: "2.4.12"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_avfoundation
|
name: video_player_avfoundation
|
||||||
sha256: "90468226c8687adf7b567d9bb42c25588783c4d30509af1fbd663b2dd049f700"
|
sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.6.1"
|
||||||
video_player_platform_interface:
|
video_player_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: video_player_platform_interface
|
path: "packages/video_player/video_player_platform_interface"
|
||||||
sha256: "318a6d20577e1c78cf0bf40670883cc571ea860c72a4f7426d7dacce4bdd4343"
|
ref: "video_player-v2.8.6-nc-photos-1"
|
||||||
url: "https://pub.dev"
|
resolved-ref: ea754fd61b8bb3c431bd33d1a07709b6f501345c
|
||||||
source: hosted
|
url: "https://gitlab.com/nc-photos/flutter-plugins"
|
||||||
version: "5.1.4"
|
source: git
|
||||||
|
version: "6.2.2"
|
||||||
video_player_web:
|
video_player_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -153,7 +153,8 @@ dependencies:
|
||||||
tuple: ^2.0.2
|
tuple: ^2.0.2
|
||||||
url_launcher: ^6.2.6
|
url_launcher: ^6.2.6
|
||||||
uuid: ^3.0.7
|
uuid: ^3.0.7
|
||||||
video_player: 2.4.5
|
video_player:
|
||||||
|
video_player_platform_interface:
|
||||||
visibility_detector: ^0.4.0+2
|
visibility_detector: ^0.4.0+2
|
||||||
wakelock_plus: ^1.1.1
|
wakelock_plus: ^1.1.1
|
||||||
woozy_search: ^2.0.3
|
woozy_search: ^2.0.3
|
||||||
|
@ -162,13 +163,18 @@ dependency_overrides:
|
||||||
video_player:
|
video_player:
|
||||||
git:
|
git:
|
||||||
url: https://gitlab.com/nc-photos/flutter-plugins
|
url: https://gitlab.com/nc-photos/flutter-plugins
|
||||||
ref: video_player-v2.4.5-nc-photos-2
|
ref: video_player-v2.8.6-nc-photos-1
|
||||||
path: packages/video_player/video_player
|
path: packages/video_player/video_player
|
||||||
video_player_android:
|
video_player_android:
|
||||||
git:
|
git:
|
||||||
url: https://gitlab.com/nc-photos/flutter-plugins
|
url: https://gitlab.com/nc-photos/flutter-plugins
|
||||||
ref: video_player-v2.4.5-nc-photos-2
|
ref: video_player-v2.8.6-nc-photos-1
|
||||||
path: packages/video_player/video_player_android
|
path: packages/video_player/video_player_android
|
||||||
|
video_player_platform_interface:
|
||||||
|
git:
|
||||||
|
url: https://gitlab.com/nc-photos/flutter-plugins
|
||||||
|
ref: video_player-v2.8.6-nc-photos-1
|
||||||
|
path: packages/video_player/video_player_platform_interface
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
test: ^1.22.1
|
test: ^1.22.1
|
||||||
|
|
Loading…
Reference in a new issue