mirror of
https://github.com/immich-app/immich.git
synced 2025-06-03 05:34:32 -04:00
feat(mobile): Fullscreen image effects (#1529)
* fullscreen image effects * toggles app bar instead of hides on tap * edgeToEdge mode to render beneath navbar on android * fixed appbar size * fixed safearea for video and added opacity to appbar in gallery * wrapped in black container to fix artifact on iOS * changed to black * added scaffold back woops
This commit is contained in:
parent
7aab84f2d9
commit
ff3cde4dfb
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@ -155,6 +156,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
|||||||
var router = ref.watch(appRouterProvider);
|
var router = ref.watch(appRouterProvider);
|
||||||
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
|
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
|
||||||
|
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
localizationsDelegates: context.localizationDelegates,
|
localizationsDelegates: context.localizationDelegates,
|
||||||
supportedLocales: context.supportedLocales,
|
supportedLocales: context.supportedLocales,
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
|
||||||
class TopControlAppBar extends HookConsumerWidget with PreferredSizeWidget {
|
class TopControlAppBar extends HookConsumerWidget {
|
||||||
const TopControlAppBar({
|
const TopControlAppBar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
@ -31,7 +31,6 @@ class TopControlAppBar extends HookConsumerWidget with PreferredSizeWidget {
|
|||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
foregroundColor: Colors.grey[100],
|
foregroundColor: Colors.grey[100],
|
||||||
toolbarHeight: 60,
|
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -120,7 +119,4 @@ class TopControlAppBar extends HookConsumerWidget with PreferredSizeWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||||
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||||
final isZoomed = useState<bool>(false);
|
final isZoomed = useState<bool>(false);
|
||||||
|
final showAppBar = useState<bool>(true);
|
||||||
final indexOfAsset = useState(assetList.indexOf(asset));
|
final indexOfAsset = useState(assetList.indexOf(asset));
|
||||||
final isPlayingMotionVideo = useState(false);
|
final isPlayingMotionVideo = useState(false);
|
||||||
late Offset localPosition;
|
late Offset localPosition;
|
||||||
@ -227,36 +228,50 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildAppBar() {
|
||||||
|
return AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
opacity: (showAppBar.value || !isZoomed.value) ? 1.0 : 0.0,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.black.withOpacity(0.4),
|
||||||
|
child: TopControlAppBar(
|
||||||
|
isPlayingMotionVideo: isPlayingMotionVideo.value,
|
||||||
|
asset: assetList[indexOfAsset.value],
|
||||||
|
onMoreInfoPressed: () {
|
||||||
|
showInfo();
|
||||||
|
},
|
||||||
|
onDownloadPressed: assetList[indexOfAsset.value].isLocal
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
ref.watch(imageViewerStateProvider.notifier).downloadAsset(
|
||||||
|
assetList[indexOfAsset.value].remote!,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSharePressed: () {
|
||||||
|
ref
|
||||||
|
.watch(imageViewerStateProvider.notifier)
|
||||||
|
.shareAsset(assetList[indexOfAsset.value], context);
|
||||||
|
},
|
||||||
|
onToggleMotionVideo: (() {
|
||||||
|
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
|
||||||
|
}),
|
||||||
|
onDeletePressed: () => handleDelete((assetList[indexOfAsset.value])),
|
||||||
|
onAddToAlbumPressed: () => addToAlbum(assetList[indexOfAsset.value]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
appBar: TopControlAppBar(
|
body: Stack(
|
||||||
isPlayingMotionVideo: isPlayingMotionVideo.value,
|
children: [
|
||||||
asset: assetList[indexOfAsset.value],
|
PhotoViewGallery.builder(
|
||||||
onMoreInfoPressed: () {
|
scaleStateChangedCallback: (state) {
|
||||||
showInfo();
|
isZoomed.value = state != PhotoViewScaleState.initial;
|
||||||
},
|
showAppBar.value = !isZoomed.value;
|
||||||
onDownloadPressed: assetList[indexOfAsset.value].isLocal
|
},
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
ref.watch(imageViewerStateProvider.notifier).downloadAsset(
|
|
||||||
assetList[indexOfAsset.value].remote!,
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onSharePressed: () {
|
|
||||||
ref
|
|
||||||
.watch(imageViewerStateProvider.notifier)
|
|
||||||
.shareAsset(assetList[indexOfAsset.value], context);
|
|
||||||
},
|
|
||||||
onToggleMotionVideo: (() {
|
|
||||||
isPlayingMotionVideo.value = !isPlayingMotionVideo.value;
|
|
||||||
}),
|
|
||||||
onDeletePressed: () => handleDelete((assetList[indexOfAsset.value])),
|
|
||||||
onAddToAlbumPressed: () => addToAlbum(assetList[indexOfAsset.value]),
|
|
||||||
),
|
|
||||||
body: SafeArea(
|
|
||||||
child: PhotoViewGallery.builder(
|
|
||||||
scaleStateChangedCallback: (state) => isZoomed.value = state != PhotoViewScaleState.initial,
|
|
||||||
pageController: controller,
|
pageController: controller,
|
||||||
scrollPhysics: isZoomed.value
|
scrollPhysics: isZoomed.value
|
||||||
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
|
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
|
||||||
@ -327,6 +342,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
return PhotoViewGalleryPageOptions(
|
return PhotoViewGalleryPageOptions(
|
||||||
onDragStart: (_, details, __) => localPosition = details.localPosition,
|
onDragStart: (_, details, __) => localPosition = details.localPosition,
|
||||||
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
||||||
|
onTapDown: (_, __, ___) => showAppBar.value = !showAppBar.value,
|
||||||
imageProvider: provider,
|
imageProvider: provider,
|
||||||
heroAttributes: PhotoViewHeroAttributes(tag: assetList[index].id),
|
heroAttributes: PhotoViewHeroAttributes(tag: assetList[index].id),
|
||||||
minScale: PhotoViewComputedScale.contained,
|
minScale: PhotoViewComputedScale.contained,
|
||||||
@ -335,20 +351,32 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
return PhotoViewGalleryPageOptions.customChild(
|
return PhotoViewGalleryPageOptions.customChild(
|
||||||
onDragStart: (_, details, __) => localPosition = details.localPosition,
|
onDragStart: (_, details, __) => localPosition = details.localPosition,
|
||||||
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
onDragUpdate: (_, details, __) => handleSwipeUpDown(details),
|
||||||
|
onTapDown: (_, __, ___) => showAppBar.value = !showAppBar.value,
|
||||||
heroAttributes: PhotoViewHeroAttributes(tag: assetList[index].id),
|
heroAttributes: PhotoViewHeroAttributes(tag: assetList[index].id),
|
||||||
child: VideoViewerPage(
|
maxScale: 1.0,
|
||||||
asset: assetList[index],
|
minScale: 1.0,
|
||||||
isMotionVideo: isPlayingMotionVideo.value,
|
child: SafeArea(
|
||||||
onVideoEnded: () {
|
child: VideoViewerPage(
|
||||||
if (isPlayingMotionVideo.value) {
|
asset: assetList[index],
|
||||||
isPlayingMotionVideo.value = false;
|
isMotionVideo: isPlayingMotionVideo.value,
|
||||||
}
|
onVideoEnded: () {
|
||||||
},
|
if (isPlayingMotionVideo.value) {
|
||||||
|
isPlayingMotionVideo.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: buildAppBar(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user