mirror of
https://github.com/immich-app/immich.git
synced 2025-12-23 09:15:05 +03:00
chore: bump dart sdk to 3.8 (#20355)
* chore: bump dart sdk to 3.8 * chore: make build * make pigeon * chore: format files --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@@ -13,7 +13,5 @@ class StackChildrenNotifier extends AutoDisposeFamilyAsyncNotifier<List<RemoteAs
|
||||
}
|
||||
}
|
||||
|
||||
final stackChildrenNotifier =
|
||||
AsyncNotifierProvider.autoDispose.family<StackChildrenNotifier, List<RemoteAsset>, BaseAsset?>(
|
||||
StackChildrenNotifier.new,
|
||||
);
|
||||
final stackChildrenNotifier = AsyncNotifierProvider.autoDispose
|
||||
.family<StackChildrenNotifier, List<RemoteAsset>, BaseAsset?>(StackChildrenNotifier.new);
|
||||
|
||||
@@ -11,9 +11,7 @@ class AssetStackRow extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
int opacity = ref.watch(
|
||||
assetViewerProvider.select((state) => state.backgroundOpacity),
|
||||
);
|
||||
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
|
||||
if (!showControls) {
|
||||
@@ -27,11 +25,10 @@ class AssetStackRow extends ConsumerWidget {
|
||||
child: AnimatedOpacity(
|
||||
opacity: opacity / 255,
|
||||
duration: Durations.short2,
|
||||
child: ref.watch(stackChildrenNotifier(asset)).when(
|
||||
data: (state) => SizedBox.square(
|
||||
dimension: 80,
|
||||
child: _StackList(stack: state),
|
||||
),
|
||||
child: ref
|
||||
.watch(stackChildrenNotifier(asset))
|
||||
.when(
|
||||
data: (state) => SizedBox.square(dimension: 80, child: _StackList(stack: state)),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
loading: () => const SizedBox.shrink(),
|
||||
),
|
||||
@@ -49,11 +46,7 @@ class _StackList extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 30),
|
||||
itemCount: stack.length,
|
||||
itemBuilder: (ctx, index) {
|
||||
final asset = stack[index];
|
||||
@@ -71,9 +64,7 @@ class _StackList extends ConsumerWidget {
|
||||
? const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: Colors.white, width: 2),
|
||||
),
|
||||
border: Border.fromBorderSide(BorderSide(color: Colors.white, width: 2)),
|
||||
)
|
||||
: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
@@ -87,10 +78,7 @@ class _StackList extends ConsumerWidget {
|
||||
children: [
|
||||
Image(
|
||||
fit: BoxFit.cover,
|
||||
image: getThumbnailImageProvider(
|
||||
remoteId: asset.id,
|
||||
size: const Size.square(60),
|
||||
),
|
||||
image: getThumbnailImageProvider(remoteId: asset.id, size: const Size.square(60)),
|
||||
),
|
||||
if (asset.isVideo)
|
||||
const Icon(
|
||||
@@ -98,11 +86,7 @@ class _StackList extends ConsumerWidget {
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
shadows: [
|
||||
Shadow(
|
||||
blurRadius: 5.0,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.6),
|
||||
offset: Offset(0.0, 0.0),
|
||||
),
|
||||
Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0)),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -36,12 +36,7 @@ class AssetViewerPage extends StatelessWidget {
|
||||
final TimelineService timelineService;
|
||||
final int? heroOffset;
|
||||
|
||||
const AssetViewerPage({
|
||||
super.key,
|
||||
required this.initialIndex,
|
||||
required this.timelineService,
|
||||
this.heroOffset,
|
||||
});
|
||||
const AssetViewerPage({super.key, required this.initialIndex, required this.timelineService, this.heroOffset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -59,12 +54,7 @@ class AssetViewer extends ConsumerStatefulWidget {
|
||||
final Platform? platform;
|
||||
final int? heroOffset;
|
||||
|
||||
const AssetViewer({
|
||||
super.key,
|
||||
required this.initialIndex,
|
||||
this.platform,
|
||||
this.heroOffset,
|
||||
});
|
||||
const AssetViewer({super.key, required this.initialIndex, this.platform, this.heroOffset});
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _AssetViewerState();
|
||||
@@ -162,11 +152,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
context,
|
||||
onError: (_, __) {},
|
||||
),
|
||||
precacheImage(
|
||||
getFullImageProvider(asset, size: screenSize),
|
||||
context,
|
||||
onError: (_, __) {},
|
||||
),
|
||||
precacheImage(getFullImageProvider(asset, size: screenSize), context, onError: (_, __) {}),
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -222,9 +208,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
duration: const Duration(seconds: 2),
|
||||
content: Text(
|
||||
"local_asset_cast_failed".tr(),
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -262,7 +246,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
viewController = controller;
|
||||
dragDownPosition = details.localPosition;
|
||||
initialPhotoViewState = controller.value;
|
||||
final isZoomed = scaleStateController.scaleState == PhotoViewScaleState.zoomedIn ||
|
||||
final isZoomed =
|
||||
scaleStateController.scaleState == PhotoViewScaleState.zoomedIn ||
|
||||
scaleStateController.scaleState == PhotoViewScaleState.covering;
|
||||
if (!showingBottomSheet && isZoomed) {
|
||||
blockGestures = true;
|
||||
@@ -350,10 +335,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
|
||||
final backgroundOpacity = (255 * (1.0 - (scaleReduction / dragRatio))).round();
|
||||
|
||||
viewController?.updateMultiple(
|
||||
position: initialPhotoViewState.position + delta,
|
||||
scale: updatedScale,
|
||||
);
|
||||
viewController?.updateMultiple(position: initialPhotoViewState.position + delta, scale: updatedScale);
|
||||
ref.read(assetViewerProvider.notifier).setOpacity(backgroundOpacity);
|
||||
}
|
||||
|
||||
@@ -450,32 +432,21 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
});
|
||||
}
|
||||
|
||||
void _openBottomSheet(
|
||||
BuildContext ctx, {
|
||||
double extent = _kBottomSheetMinimumExtent,
|
||||
}) {
|
||||
void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent}) {
|
||||
ref.read(assetViewerProvider.notifier).setBottomSheet(true);
|
||||
initialScale = viewController?.scale;
|
||||
viewController?.updateMultiple(scale: _getScaleForBottomSheet);
|
||||
previousExtent = _kBottomSheetMinimumExtent;
|
||||
sheetCloseController = showBottomSheet(
|
||||
context: ctx,
|
||||
sheetAnimationStyle: const AnimationStyle(
|
||||
duration: Durations.short4,
|
||||
reverseDuration: Durations.short2,
|
||||
),
|
||||
sheetAnimationStyle: const AnimationStyle(duration: Durations.short4, reverseDuration: Durations.short2),
|
||||
constraints: const BoxConstraints(maxWidth: double.infinity),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20.0)),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20.0))),
|
||||
backgroundColor: ctx.colorScheme.surfaceContainerLowest,
|
||||
builder: (_) {
|
||||
return NotificationListener<Notification>(
|
||||
onNotification: _onNotification,
|
||||
child: AssetDetailBottomSheet(
|
||||
controller: bottomSheetController,
|
||||
initialChildSize: extent,
|
||||
),
|
||||
child: AssetDetailBottomSheet(controller: bottomSheetController, initialChildSize: extent),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -496,18 +467,10 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
return;
|
||||
}
|
||||
isSnapping = true;
|
||||
bottomSheetController.animateTo(
|
||||
_kBottomSheetSnapExtent,
|
||||
duration: Durations.short3,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
bottomSheetController.animateTo(_kBottomSheetSnapExtent, duration: Durations.short3, curve: Curves.easeOut);
|
||||
}
|
||||
|
||||
Widget _placeholderBuilder(
|
||||
BuildContext ctx,
|
||||
ImageChunkEvent? progress,
|
||||
int index,
|
||||
) {
|
||||
Widget _placeholderBuilder(BuildContext ctx, ImageChunkEvent? progress, int index) {
|
||||
BaseAsset asset = ref.read(timelineServiceProvider).getAsset(index);
|
||||
final stackChildren = ref.read(stackChildrenNotifier(asset)).valueOrNull;
|
||||
if (stackChildren != null && stackChildren.isNotEmpty) {
|
||||
@@ -517,14 +480,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
color: backgroundColor,
|
||||
child: Thumbnail(
|
||||
asset: asset,
|
||||
fit: BoxFit.contain,
|
||||
size: Size(
|
||||
ctx.width,
|
||||
ctx.height,
|
||||
),
|
||||
),
|
||||
child: Thumbnail(asset: asset, fit: BoxFit.contain, size: Size(ctx.width, ctx.height)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -574,11 +530,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
width: ctx.width,
|
||||
height: ctx.height,
|
||||
color: backgroundColor,
|
||||
child: Thumbnail(
|
||||
asset: asset,
|
||||
fit: BoxFit.contain,
|
||||
size: size,
|
||||
),
|
||||
child: Thumbnail(asset: asset, fit: BoxFit.contain, size: size),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -662,8 +614,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
pageController: pageController,
|
||||
scrollPhysics: platform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics() // Use heavy physics for Android
|
||||
,
|
||||
: const FastClampingScrollPhysics(), // Use heavy physics for Android
|
||||
itemCount: totalAssets,
|
||||
onPageChanged: _onPageChanged,
|
||||
onPageBuild: _onPageBuild,
|
||||
@@ -678,10 +629,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const AssetStackRow(),
|
||||
if (!isInLockedView) const ViewerBottomBar(),
|
||||
],
|
||||
children: [const AssetStackRow(), if (!isInLockedView) const ViewerBottomBar()],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -79,17 +79,11 @@ class AssetViewerStateNotifier extends AutoDisposeNotifier<AssetViewerState> {
|
||||
}
|
||||
|
||||
void setOpacity(int opacity) {
|
||||
state = state.copyWith(
|
||||
backgroundOpacity: opacity,
|
||||
showingControls: opacity == 255 ? true : state.showingControls,
|
||||
);
|
||||
state = state.copyWith(backgroundOpacity: opacity, showingControls: opacity == 255 ? true : state.showingControls);
|
||||
}
|
||||
|
||||
void setBottomSheet(bool showing) {
|
||||
state = state.copyWith(
|
||||
showingBottomSheet: showing,
|
||||
showingControls: showing ? true : state.showingControls,
|
||||
);
|
||||
state = state.copyWith(showingBottomSheet: showing, showingControls: showing ? true : state.showingControls);
|
||||
if (showing) {
|
||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||
}
|
||||
|
||||
@@ -25,12 +25,8 @@ class ViewerBottomBar extends ConsumerWidget {
|
||||
|
||||
final user = ref.watch(currentUserProvider);
|
||||
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
||||
final isSheetOpen = ref.watch(
|
||||
assetViewerProvider.select((s) => s.showingBottomSheet),
|
||||
);
|
||||
int opacity = ref.watch(
|
||||
assetViewerProvider.select((state) => state.backgroundOpacity),
|
||||
);
|
||||
final isSheetOpen = ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
|
||||
if (!showControls) {
|
||||
@@ -42,13 +38,8 @@ class ViewerBottomBar extends ConsumerWidget {
|
||||
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
|
||||
if (asset.hasRemote && isOwner) const ArchiveActionButton(source: ActionSource.viewer),
|
||||
asset.isLocalOnly
|
||||
? const DeleteLocalActionButton(
|
||||
source: ActionSource.viewer,
|
||||
)
|
||||
: const DeleteActionButton(
|
||||
source: ActionSource.viewer,
|
||||
showConfirmation: true,
|
||||
),
|
||||
? const DeleteLocalActionButton(source: ActionSource.viewer)
|
||||
: const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true),
|
||||
];
|
||||
|
||||
return IgnorePointer(
|
||||
@@ -64,9 +55,7 @@ class ViewerBottomBar extends ConsumerWidget {
|
||||
data: context.themeData.copyWith(
|
||||
iconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||
textTheme: context.themeData.textTheme.copyWith(
|
||||
labelLarge: context.themeData.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
labelLarge: context.themeData.textTheme.labelLarge?.copyWith(color: Colors.white),
|
||||
),
|
||||
),
|
||||
child: Container(
|
||||
@@ -77,10 +66,7 @@ class ViewerBottomBar extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (asset.isVideo) const VideoControls(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: actions,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: actions),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -29,11 +29,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
final DraggableScrollableController? controller;
|
||||
final double initialChildSize;
|
||||
|
||||
const AssetDetailBottomSheet({
|
||||
this.controller,
|
||||
this.initialChildSize = 0.35,
|
||||
super.key,
|
||||
});
|
||||
const AssetDetailBottomSheet({this.controller, this.initialChildSize = 0.35, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -42,9 +38,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final isTrashEnable = ref.watch(
|
||||
serverInfoProvider.select((state) => state.serverFeatures.trash),
|
||||
);
|
||||
final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash));
|
||||
|
||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||
|
||||
@@ -58,9 +52,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
||||
? const TrashActionButton(source: ActionSource.viewer)
|
||||
: const DeletePermanentActionButton(source: ActionSource.viewer),
|
||||
const DeleteActionButton(source: ActionSource.viewer),
|
||||
const MoveToLockFolderActionButton(
|
||||
source: ActionSource.viewer,
|
||||
),
|
||||
const MoveToLockFolderActionButton(source: ActionSource.viewer),
|
||||
],
|
||||
if (asset.storage == AssetState.local) ...[
|
||||
const DeleteLocalActionButton(source: ActionSource.viewer),
|
||||
@@ -153,9 +145,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
// Asset Date and Time
|
||||
_SheetTile(
|
||||
title: _getDateTime(context, asset),
|
||||
titleStyle: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
titleStyle: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SheetLocationDetails(),
|
||||
// Details header
|
||||
@@ -185,11 +175,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
_SheetTile(
|
||||
title: cameraTitle,
|
||||
titleStyle: context.textTheme.labelLarge,
|
||||
leading: Icon(
|
||||
Icons.camera_outlined,
|
||||
size: 24,
|
||||
color: context.textTheme.labelLarge?.color,
|
||||
),
|
||||
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||
subtitle: _getCameraInfoSubtitle(exifInfo),
|
||||
subtitleStyle: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.textTheme.bodyMedium?.color?.withAlpha(155),
|
||||
@@ -207,13 +193,7 @@ class _SheetTile extends StatelessWidget {
|
||||
final TextStyle? titleStyle;
|
||||
final TextStyle? subtitleStyle;
|
||||
|
||||
const _SheetTile({
|
||||
required this.title,
|
||||
this.titleStyle,
|
||||
this.leading,
|
||||
this.subtitle,
|
||||
this.subtitleStyle,
|
||||
});
|
||||
const _SheetTile({required this.title, this.titleStyle, this.leading, this.subtitle, this.subtitleStyle});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -38,20 +38,13 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
||||
_mapController = controller;
|
||||
}
|
||||
|
||||
void _onExifChanged(
|
||||
AsyncValue<ExifInfo?>? previous,
|
||||
AsyncValue<ExifInfo?> current,
|
||||
) {
|
||||
void _onExifChanged(AsyncValue<ExifInfo?>? previous, AsyncValue<ExifInfo?> current) {
|
||||
asset = ref.read(currentAssetNotifier);
|
||||
setState(() {
|
||||
exifInfo = current.valueOrNull;
|
||||
final hasCoordinates = exifInfo?.hasCoordinates ?? false;
|
||||
if (exifInfo != null && hasCoordinates) {
|
||||
_mapController?.moveCamera(
|
||||
CameraUpdate.newLatLng(
|
||||
LatLng(exifInfo!.latitude!, exifInfo!.longitude!),
|
||||
),
|
||||
);
|
||||
_mapController?.moveCamera(CameraUpdate.newLatLng(LatLng(exifInfo!.latitude!, exifInfo!.longitude!)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -59,11 +52,7 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ref.listenManual(
|
||||
currentAssetExifProvider,
|
||||
_onExifChanged,
|
||||
fireImmediately: true,
|
||||
);
|
||||
ref.listenManual(currentAssetExifProvider, _onExifChanged, fireImmediately: true);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -80,10 +69,7 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
||||
final coordinates = "${exifInfo!.latitude!.toStringAsFixed(4)}, ${exifInfo!.longitude!.toStringAsFixed(4)}";
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 16.0,
|
||||
horizontal: context.isMobile ? 16.0 : 56.0,
|
||||
),
|
||||
padding: EdgeInsets.symmetric(vertical: 16.0, horizontal: context.isMobile ? 16.0 : 56.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -97,25 +83,16 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
||||
),
|
||||
),
|
||||
),
|
||||
ExifMap(
|
||||
exifInfo: exifInfo!,
|
||||
markerId: remoteId,
|
||||
onMapCreated: _onMapCreated,
|
||||
),
|
||||
ExifMap(exifInfo: exifInfo!, markerId: remoteId, onMapCreated: _onMapCreated),
|
||||
const SizedBox(height: 15),
|
||||
if (locationName != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Text(
|
||||
locationName,
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
child: Text(locationName, style: context.textTheme.labelLarge),
|
||||
),
|
||||
Text(
|
||||
coordinates,
|
||||
style: context.textTheme.labelMedium?.copyWith(
|
||||
color: context.textTheme.labelMedium?.color?.withAlpha(150),
|
||||
),
|
||||
style: context.textTheme.labelMedium?.copyWith(color: context.textTheme.labelMedium?.color?.withAlpha(150)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -36,25 +36,18 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
final showViewInTimelineButton = previousRouteName != TabShellRoute.name && previousRouteName != null;
|
||||
|
||||
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
int opacity = ref.watch(
|
||||
assetViewerProvider.select((state) => state.backgroundOpacity),
|
||||
);
|
||||
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
|
||||
if (!showControls) {
|
||||
opacity = 0;
|
||||
}
|
||||
|
||||
final isCasting = ref.watch(
|
||||
castProvider.select((c) => c.isCasting),
|
||||
);
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
final websocketConnected = ref.watch(websocketProvider.select((c) => c.isConnected));
|
||||
|
||||
final actions = <Widget>[
|
||||
if (isCasting || (asset.hasRemote && websocketConnected))
|
||||
const CastActionButton(
|
||||
menuItem: true,
|
||||
),
|
||||
if (isCasting || (asset.hasRemote && websocketConnected)) const CastActionButton(menuItem: true),
|
||||
if (showViewInTimelineButton)
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
@@ -68,19 +61,13 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
if (asset.hasRemote && isOwner && !asset.isFavorite)
|
||||
const FavoriteActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
if (asset.hasRemote && isOwner && asset.isFavorite)
|
||||
const UnFavoriteActionButton(
|
||||
source: ActionSource.viewer,
|
||||
menuItem: true,
|
||||
),
|
||||
const UnFavoriteActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
if (asset.isMotionPhoto) const MotionPhotoActionButton(menuItem: true),
|
||||
const _KebabMenu(),
|
||||
];
|
||||
|
||||
final lockedViewActions = <Widget>[
|
||||
if (isCasting || (asset.hasRemote && websocketConnected))
|
||||
const CastActionButton(
|
||||
menuItem: true,
|
||||
),
|
||||
if (isCasting || (asset.hasRemote && websocketConnected)) const CastActionButton(menuItem: true),
|
||||
const _KebabMenu(),
|
||||
];
|
||||
|
||||
@@ -98,8 +85,8 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
actions: isShowingSheet
|
||||
? null
|
||||
: isInLockedView
|
||||
? lockedViewActions
|
||||
: actions,
|
||||
? lockedViewActions
|
||||
: actions,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -27,10 +27,7 @@ import 'package:logging/logging.dart';
|
||||
import 'package:native_video_player/native_video_player.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
bool _isCurrentAsset(
|
||||
BaseAsset asset,
|
||||
BaseAsset? currentAsset,
|
||||
) {
|
||||
bool _isCurrentAsset(BaseAsset asset, BaseAsset? currentAsset) {
|
||||
if (asset is RemoteAsset) {
|
||||
return switch (currentAsset) {
|
||||
RemoteAsset remoteAsset => remoteAsset.id == asset.id,
|
||||
@@ -98,10 +95,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
throw Exception('No file found for the video');
|
||||
}
|
||||
|
||||
final source = await VideoSource.init(
|
||||
path: file.path,
|
||||
type: VideoSourceType.file,
|
||||
);
|
||||
final source = await VideoSource.init(path: file.path, type: VideoSourceType.file);
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -122,31 +116,24 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
);
|
||||
return source;
|
||||
} catch (error) {
|
||||
log.severe(
|
||||
'Error creating video source for asset ${asset.name}: $error',
|
||||
);
|
||||
log.severe('Error creating video source for asset ${asset.name}: $error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final videoSource = useMemoized<Future<VideoSource?>>(() => createSource());
|
||||
final aspectRatio = useState<double?>(null);
|
||||
useMemoized(
|
||||
() async {
|
||||
if (!context.mounted || aspectRatio.value != null) {
|
||||
return null;
|
||||
}
|
||||
useMemoized(() async {
|
||||
if (!context.mounted || aspectRatio.value != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
aspectRatio.value = await ref.read(assetServiceProvider).getAspectRatio(asset);
|
||||
} catch (error) {
|
||||
log.severe(
|
||||
'Error getting aspect ratio for asset ${asset.name}: $error',
|
||||
);
|
||||
}
|
||||
},
|
||||
[asset.heroTag],
|
||||
);
|
||||
try {
|
||||
aspectRatio.value = await ref.read(assetServiceProvider).getAspectRatio(asset);
|
||||
} catch (error) {
|
||||
log.severe('Error getting aspect ratio for asset ${asset.name}: $error');
|
||||
}
|
||||
}, [asset.heroTag]);
|
||||
|
||||
void checkIfBuffering() {
|
||||
if (!context.mounted) {
|
||||
@@ -156,8 +143,9 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
final videoPlayback = ref.read(videoPlaybackValueProvider);
|
||||
if ((isBuffering.value || videoPlayback.state == VideoPlaybackState.initializing) &&
|
||||
videoPlayback.state != VideoPlaybackState.buffering) {
|
||||
ref.read(videoPlaybackValueProvider.notifier).value =
|
||||
videoPlayback.copyWith(state: VideoPlaybackState.buffering);
|
||||
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback.copyWith(
|
||||
state: VideoPlaybackState.buffering,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,48 +333,42 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
// This delay seems like a hacky way to resolve underlying bugs in video
|
||||
// playback, but other resolutions failed thus far
|
||||
Timer(
|
||||
Platform.isIOS
|
||||
? Duration(milliseconds: 300 * playbackDelayFactor)
|
||||
: imageToVideo
|
||||
? Duration(milliseconds: 200 * playbackDelayFactor)
|
||||
: Duration(milliseconds: 400 * playbackDelayFactor), () {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentAsset.value = value;
|
||||
if (currentAsset.value == asset) {
|
||||
onPlaybackReady();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
// If opening a remote video from a hero animation, delay visibility to avoid a stutter
|
||||
final timer = isVisible.value
|
||||
? null
|
||||
: Timer(
|
||||
const Duration(milliseconds: 300),
|
||||
() => isVisible.value = true,
|
||||
);
|
||||
|
||||
return () {
|
||||
timer?.cancel();
|
||||
final playerController = controller.value;
|
||||
if (playerController == null) {
|
||||
Platform.isIOS
|
||||
? Duration(milliseconds: 300 * playbackDelayFactor)
|
||||
: imageToVideo
|
||||
? Duration(milliseconds: 200 * playbackDelayFactor)
|
||||
: Duration(milliseconds: 400 * playbackDelayFactor),
|
||||
() {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
removeListeners(playerController);
|
||||
playerController.stop().catchError((error) {
|
||||
log.fine('Error stopping video: $error');
|
||||
});
|
||||
|
||||
WakelockPlus.disable();
|
||||
};
|
||||
},
|
||||
const [],
|
||||
);
|
||||
currentAsset.value = value;
|
||||
if (currentAsset.value == asset) {
|
||||
onPlaybackReady();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() {
|
||||
// If opening a remote video from a hero animation, delay visibility to avoid a stutter
|
||||
final timer = isVisible.value ? null : Timer(const Duration(milliseconds: 300), () => isVisible.value = true);
|
||||
|
||||
return () {
|
||||
timer?.cancel();
|
||||
final playerController = controller.value;
|
||||
if (playerController == null) {
|
||||
return;
|
||||
}
|
||||
removeListeners(playerController);
|
||||
playerController.stop().catchError((error) {
|
||||
log.fine('Error stopping video: $error');
|
||||
});
|
||||
|
||||
WakelockPlus.disable();
|
||||
};
|
||||
}, const []);
|
||||
|
||||
useOnAppLifecycleStateChange((_, state) async {
|
||||
if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) {
|
||||
@@ -416,12 +398,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
||||
child: AspectRatio(
|
||||
key: ValueKey(asset),
|
||||
aspectRatio: aspectRatio.value!,
|
||||
child: isCurrent
|
||||
? NativeVideoPlayerView(
|
||||
key: ValueKey(asset),
|
||||
onViewReady: initController,
|
||||
)
|
||||
: null,
|
||||
child: isCurrent ? NativeVideoPlayerView(key: ValueKey(asset), onViewReady: initController) : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -13,16 +13,11 @@ import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
||||
class VideoViewerControls extends HookConsumerWidget {
|
||||
final Duration hideTimerDuration;
|
||||
|
||||
const VideoViewerControls({
|
||||
super.key,
|
||||
this.hideTimerDuration = const Duration(seconds: 5),
|
||||
});
|
||||
const VideoViewerControls({super.key, this.hideTimerDuration = const Duration(seconds: 5)});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assetIsVideo = ref.watch(
|
||||
currentAssetNotifier.select((asset) => asset != null && asset.isVideo),
|
||||
);
|
||||
final assetIsVideo = ref.watch(currentAssetNotifier.select((asset) => asset != null && asset.isVideo));
|
||||
bool showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
final showBottomSheet = ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
if (showBottomSheet) {
|
||||
@@ -33,20 +28,17 @@ class VideoViewerControls extends HookConsumerWidget {
|
||||
final cast = ref.watch(castProvider);
|
||||
|
||||
// A timer to hide the controls
|
||||
final hideTimer = useTimer(
|
||||
hideTimerDuration,
|
||||
() {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
final state = ref.read(videoPlaybackValueProvider).state;
|
||||
final hideTimer = useTimer(hideTimerDuration, () {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
final state = ref.read(videoPlaybackValueProvider).state;
|
||||
|
||||
// Do not hide on paused
|
||||
if (state != VideoPlaybackState.paused && state != VideoPlaybackState.completed && assetIsVideo) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
// Do not hide on paused
|
||||
if (state != VideoPlaybackState.paused && state != VideoPlaybackState.completed && assetIsVideo) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||
}
|
||||
});
|
||||
final showBuffering = state == VideoPlaybackState.buffering && !cast.isCasting;
|
||||
|
||||
/// Shows the controls and starts the timer to hide them
|
||||
@@ -97,11 +89,7 @@ class VideoViewerControls extends HookConsumerWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
if (showBuffering)
|
||||
const Center(
|
||||
child: DelayedLoadingIndicator(
|
||||
fadeInDuration: Duration(milliseconds: 400),
|
||||
),
|
||||
)
|
||||
const Center(child: DelayedLoadingIndicator(fadeInDuration: Duration(milliseconds: 400)))
|
||||
else
|
||||
GestureDetector(
|
||||
onTap: () => ref.read(assetViewerProvider.notifier).setControls(false),
|
||||
|
||||
Reference in New Issue
Block a user