mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
feat: show "appears in" albums on asset viewer bottom sheet (#21925)
* feat: show "appears in" albums on asset viewer bottom sheet fix: multiple RemoteAlbumPages in navigation stack this also allows us to not have to set the current album before navigating to RemoteAlbumPage chore: clarification comments handle nested album pages fix: hide "appears in" when an asset is not in any albums fix: way more bottom padding for some reason we can't query the safe area here :/ * fix: bottom sheet now is usable when navigating to another asset viewer * fix: rebase conflict * fix: restore ancestors album to currentRemoteAlbumProvider when popping * fix: view flashing when dismissing a album viewer * chore: code review changes * fix: styling and padding * chore: rework currentRemoteAlbumProvider to be scoped by the Remote album page * fix: override remote album provider on required pages * chore: convert query to all SQL calls instead of matching in Dart * fix: album query * fix: unawaited future * Update deep_link.service.dart --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
@@ -18,12 +19,13 @@ import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftActivitiesPage extends HookConsumerWidget {
|
||||
const DriftActivitiesPage({super.key});
|
||||
final RemoteAlbum album;
|
||||
|
||||
const DriftActivitiesPage({super.key, required this.album});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final album = ref.watch(currentRemoteAlbumProvider)!;
|
||||
final asset = ref.read(currentAssetNotifier) as RemoteAsset?;
|
||||
final asset = ref.watch(currentAssetNotifier) as RemoteAsset?;
|
||||
final user = ref.watch(currentUserProvider);
|
||||
|
||||
final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier);
|
||||
@@ -43,62 +45,65 @@ class DriftActivitiesPage extends HookConsumerWidget {
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: asset == null ? Text(album.name) : null,
|
||||
actions: [const LikeActivityActionButton(menuItem: true)],
|
||||
actionsPadding: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
body: activities.widgetWhen(
|
||||
onData: (data) {
|
||||
final liked = data.firstWhereOrNull(
|
||||
(a) => a.type == ActivityType.like && a.user.id == user?.id && a.assetId == asset?.id,
|
||||
);
|
||||
return ProviderScope(
|
||||
overrides: [currentRemoteAlbumScopedProvider.overrideWithValue(album)],
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: asset == null ? Text(album.name) : null,
|
||||
actions: [const LikeActivityActionButton(menuItem: true)],
|
||||
actionsPadding: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
body: activities.widgetWhen(
|
||||
onData: (data) {
|
||||
final liked = data.firstWhereOrNull(
|
||||
(a) => a.type == ActivityType.like && a.user.id == user?.id && a.assetId == asset?.id,
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
ListView.builder(
|
||||
controller: listViewScrollController,
|
||||
itemCount: data.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == data.length) {
|
||||
return const SizedBox(height: 80);
|
||||
}
|
||||
final activity = data[index];
|
||||
final canDelete = activity.user.id == user?.id || album.ownerId == user?.id;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: DismissibleActivity(
|
||||
activity.id,
|
||||
ActivityTile(activity),
|
||||
onDismiss: canDelete
|
||||
? (activityId) async => await activityNotifier.removeActivity(activity.id)
|
||||
: null,
|
||||
return SafeArea(
|
||||
child: Stack(
|
||||
children: [
|
||||
ListView.builder(
|
||||
controller: listViewScrollController,
|
||||
itemCount: data.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == data.length) {
|
||||
return const SizedBox(height: 80);
|
||||
}
|
||||
final activity = data[index];
|
||||
final canDelete = activity.user.id == user?.id || album.ownerId == user?.id;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(5),
|
||||
child: DismissibleActivity(
|
||||
activity.id,
|
||||
ActivityTile(activity),
|
||||
onDismiss: canDelete
|
||||
? (activityId) async => await activityNotifier.removeActivity(activity.id)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.scaffoldBackgroundColor,
|
||||
border: Border(top: BorderSide(color: context.colorScheme.secondaryContainer, width: 1)),
|
||||
),
|
||||
child: DriftActivityTextField(
|
||||
isEnabled: album.isActivityEnabled,
|
||||
likeId: liked?.id,
|
||||
onSubmit: onAddComment,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.scaffoldBackgroundColor,
|
||||
border: Border(top: BorderSide(color: context.colorScheme.secondaryContainer, width: 1)),
|
||||
),
|
||||
child: DriftActivityTextField(
|
||||
isEnabled: album.isActivityEnabled,
|
||||
likeId: liked?.id,
|
||||
onSubmit: onAddComment,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
resizeToAvoidBottomInset: true,
|
||||
),
|
||||
resizeToAvoidBottomInset: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
||||
|
||||
@@ -43,7 +42,6 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
|
||||
),
|
||||
AlbumSelector(
|
||||
onAlbumSelected: (album) {
|
||||
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album);
|
||||
context.router.push(RemoteAlbumRoute(album: album));
|
||||
},
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
@@ -22,15 +23,11 @@ import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftAlbumOptionsPage extends HookConsumerWidget {
|
||||
const DriftAlbumOptionsPage({super.key});
|
||||
final RemoteAlbum album;
|
||||
const DriftAlbumOptionsPage({super.key, required this.album});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final album = ref.watch(currentRemoteAlbumProvider);
|
||||
if (album == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final sharedUsersAsync = ref.watch(remoteAlbumSharedUsersProvider(album.id));
|
||||
final userId = ref.watch(authProvider).userId;
|
||||
final activityEnabled = useState(album.isActivityEnabled);
|
||||
@@ -191,48 +188,51 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
onPressed: () => context.maybePop(null),
|
||||
return ProviderScope(
|
||||
overrides: [currentRemoteAlbumScopedProvider.overrideWithValue(album)],
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
onPressed: () => context.maybePop(null),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text("options".t(context: context)),
|
||||
),
|
||||
centerTitle: true,
|
||||
title: Text("options".t(context: context)),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
if (isOwner)
|
||||
SwitchListTile.adaptive(
|
||||
value: activityEnabled.value,
|
||||
onChanged: (bool value) async {
|
||||
activityEnabled.value = value;
|
||||
await ref.read(remoteAlbumProvider.notifier).setActivityStatus(album.id, value);
|
||||
},
|
||||
activeThumbColor: activityEnabled.value ? context.primaryColor : context.themeData.disabledColor,
|
||||
dense: true,
|
||||
title: Text(
|
||||
"comments_and_likes",
|
||||
style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
|
||||
).t(context: context),
|
||||
subtitle: Text(
|
||||
"let_others_respond",
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
).t(context: context),
|
||||
),
|
||||
buildSectionTitle("shared_album_section_people_title".t(context: context)),
|
||||
if (isOwner) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_add_rounded),
|
||||
title: Text("invite_people".t(context: context)),
|
||||
onTap: () async => addUsers(),
|
||||
),
|
||||
const Divider(indent: 16),
|
||||
body: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
if (isOwner)
|
||||
SwitchListTile.adaptive(
|
||||
value: activityEnabled.value,
|
||||
onChanged: (bool value) async {
|
||||
activityEnabled.value = value;
|
||||
await ref.read(remoteAlbumProvider.notifier).setActivityStatus(album.id, value);
|
||||
},
|
||||
activeThumbColor: activityEnabled.value ? context.primaryColor : context.themeData.disabledColor,
|
||||
dense: true,
|
||||
title: Text(
|
||||
"comments_and_likes",
|
||||
style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500),
|
||||
).t(context: context),
|
||||
subtitle: Text(
|
||||
"let_others_respond",
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
).t(context: context),
|
||||
),
|
||||
buildSectionTitle("shared_album_section_people_title".t(context: context)),
|
||||
if (isOwner) ...[
|
||||
ListTile(
|
||||
leading: const Icon(Icons.person_add_rounded),
|
||||
title: Text("invite_people".t(context: context)),
|
||||
onTap: () async => addUsers(),
|
||||
),
|
||||
const Divider(indent: 16),
|
||||
],
|
||||
buildOwnerInfo(),
|
||||
buildSharedUsersList(),
|
||||
],
|
||||
buildOwnerInfo(),
|
||||
buildSharedUsersList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/album/album_action_filled_button.dart';
|
||||
|
||||
@@ -180,7 +179,6 @@ class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> {
|
||||
);
|
||||
|
||||
if (album != null) {
|
||||
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(album);
|
||||
unawaited(context.replaceRoute(RemoteAlbumRoute(album: album)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
}
|
||||
|
||||
Future<void> showActivity(BuildContext context) async {
|
||||
unawaited(context.pushRoute(const DriftActivitiesRoute()));
|
||||
unawaited(context.pushRoute(DriftActivitiesRoute(album: _album)));
|
||||
}
|
||||
|
||||
Future<void> showOptionSheet(BuildContext context) async {
|
||||
@@ -224,7 +224,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
: null,
|
||||
onShowOptions: () {
|
||||
context.pop();
|
||||
context.pushRoute(const DriftAlbumOptionsRoute());
|
||||
context.pushRoute(DriftAlbumOptionsRoute(album: _album));
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -237,35 +237,24 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
final isOwner = user != null ? user.id == _album.ownerId : false;
|
||||
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, _) {
|
||||
if (didPop) {
|
||||
Future.microtask(() {
|
||||
if (mounted) {
|
||||
ref.read(currentRemoteAlbumProvider.notifier).dispose();
|
||||
ref.read(remoteAlbumProvider.notifier).refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
child: ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith((ref) {
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAlbum(albumId: _album.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
}),
|
||||
],
|
||||
child: Timeline(
|
||||
appBar: RemoteAlbumSliverAppBar(
|
||||
icon: Icons.photo_album_outlined,
|
||||
onShowOptions: () => showOptionSheet(context),
|
||||
onToggleAlbumOrder: isOwner ? () => toggleAlbumOrder() : null,
|
||||
onEditTitle: isOwner ? () => showEditTitleAndDescription(context) : null,
|
||||
onActivity: () => showActivity(context),
|
||||
),
|
||||
bottomSheet: RemoteAlbumBottomSheet(album: _album),
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith((ref) {
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAlbum(albumId: _album.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
}),
|
||||
currentRemoteAlbumScopedProvider.overrideWithValue(_album),
|
||||
],
|
||||
child: Timeline(
|
||||
appBar: RemoteAlbumSliverAppBar(
|
||||
icon: Icons.photo_album_outlined,
|
||||
onShowOptions: () => showOptionSheet(context),
|
||||
onToggleAlbumOrder: isOwner ? () => toggleAlbumOrder() : null,
|
||||
onEditTitle: isOwner ? () => showEditTitleAndDescription(context) : null,
|
||||
onActivity: () => showActivity(context),
|
||||
),
|
||||
bottomSheet: RemoteAlbumBottomSheet(album: _album),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,9 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/models/albums/album_search.model.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
@@ -516,38 +515,6 @@ class _AlbumList extends ConsumerWidget {
|
||||
sliver: SliverList.builder(
|
||||
itemBuilder: (_, index) {
|
||||
final album = albums[index];
|
||||
final albumTile = LargeLeadingTile(
|
||||
title: Text(
|
||||
album.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${'items_count'.t(context: context, args: {'count': album.assetCount})} • ${album.ownerId != userId ? 'shared_by_user'.t(context: context, args: {'user': album.ownerName}) : 'owned'.t(context: context)}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
onTap: () => onAlbumSelected(album),
|
||||
leadingPadding: const EdgeInsets.only(right: 16),
|
||||
leading: album.thumbnailAssetId != null
|
||||
? ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
child: SizedBox(width: 80, height: 80, child: Thumbnail.remote(remoteId: album.thumbnailAssetId!)),
|
||||
)
|
||||
: SizedBox(
|
||||
width: 80,
|
||||
height: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1),
|
||||
),
|
||||
child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
);
|
||||
final isOwner = album.ownerId == userId;
|
||||
|
||||
if (isOwner) {
|
||||
@@ -576,11 +543,14 @@ class _AlbumList extends ConsumerWidget {
|
||||
onDismissed: (direction) async {
|
||||
await ref.read(remoteAlbumProvider.notifier).deleteAlbum(album.id);
|
||||
},
|
||||
child: albumTile,
|
||||
child: AlbumTile(album: album, isOwner: isOwner, onAlbumSelected: onAlbumSelected),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(padding: const EdgeInsets.only(bottom: 8.0), child: albumTile);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: AlbumTile(album: album, isOwner: isOwner, onAlbumSelected: onAlbumSelected),
|
||||
);
|
||||
}
|
||||
},
|
||||
itemCount: albums.length,
|
||||
@@ -709,7 +679,6 @@ class AddToAlbumHeader extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(newAlbum);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
unawaited(context.pushRoute(RemoteAlbumRoute(album: newAlbum)));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
|
||||
class AlbumTile extends StatelessWidget {
|
||||
const AlbumTile({super.key, required this.album, required this.isOwner, this.onAlbumSelected});
|
||||
|
||||
final RemoteAlbum album;
|
||||
final bool isOwner;
|
||||
final Function(RemoteAlbum)? onAlbumSelected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LargeLeadingTile(
|
||||
title: Text(
|
||||
album.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
'${'items_count'.t(context: context, args: {'count': album.assetCount})} • ${isOwner ? 'owned'.t(context: context) : 'shared_by_user'.t(context: context, args: {'user': album.ownerName})}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
onTap: () => onAlbumSelected?.call(album),
|
||||
leadingPadding: const EdgeInsets.only(right: 16),
|
||||
leading: album.thumbnailAssetId != null
|
||||
? ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
child: SizedBox(width: 80, height: 80, child: Thumbnail.remote(remoteId: album.thumbnailAssetId!)),
|
||||
)
|
||||
: SizedBox(
|
||||
width: 80,
|
||||
height: 80,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1),
|
||||
),
|
||||
child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
@@ -13,6 +14,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
@@ -20,7 +22,6 @@ import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widge
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/top_app_bar.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/activities_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||
@@ -28,6 +29,7 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_controls_provi
|
||||
import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider.dart';
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||
@@ -39,15 +41,25 @@ class AssetViewerPage extends StatelessWidget {
|
||||
final int initialIndex;
|
||||
final TimelineService timelineService;
|
||||
final int? heroOffset;
|
||||
final RemoteAlbum? currentAlbum;
|
||||
|
||||
const AssetViewerPage({super.key, required this.initialIndex, required this.timelineService, this.heroOffset});
|
||||
const AssetViewerPage({
|
||||
super.key,
|
||||
required this.initialIndex,
|
||||
required this.timelineService,
|
||||
this.heroOffset,
|
||||
this.currentAlbum,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This is necessary to ensure that the timeline service is available
|
||||
// since the Timeline and AssetViewer are on different routes / Widget subtrees.
|
||||
return ProviderScope(
|
||||
overrides: [timelineServiceProvider.overrideWithValue(timelineService)],
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWithValue(timelineService),
|
||||
currentRemoteAlbumScopedProvider.overrideWithValue(currentAlbum),
|
||||
],
|
||||
child: AssetViewer(initialIndex: initialIndex, heroOffset: heroOffset),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -8,11 +12,14 @@ import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_location_details.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||
@@ -20,6 +27,7 @@ import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/action_button.utils.dart';
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
@@ -132,6 +140,60 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
await ref.read(actionProvider.notifier).editDateTime(ActionSource.viewer, context);
|
||||
}
|
||||
|
||||
Widget _buildAppearsInList(WidgetRef ref, BuildContext context) {
|
||||
final isRemote = ref.watch(currentAssetNotifier)?.hasRemote ?? false;
|
||||
if (!isRemote) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final remoteAsset = ref.watch(currentAssetNotifier) as RemoteAsset;
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
final assetAlbums = ref.watch(albumsContainingAssetProvider(remoteAsset.id));
|
||||
|
||||
return assetAlbums.when(
|
||||
data: (albums) {
|
||||
if (albums.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
albums.sortBy((a) => a.name);
|
||||
|
||||
return Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
if (albums.isNotEmpty)
|
||||
_SheetTile(
|
||||
title: 'appears_in'.t(context: context).toUpperCase(),
|
||||
titleStyle: context.textTheme.labelMedium?.copyWith(
|
||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: albums.map((album) {
|
||||
final isOwner = album.ownerId == userId;
|
||||
return AlbumTile(
|
||||
album: album,
|
||||
isOwner: isOwner,
|
||||
onAlbumSelected: (album) async {
|
||||
ref.invalidate(assetViewerProvider);
|
||||
unawaited(context.router.popAndPush(RemoteAlbumRoute(album: album)));
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final asset = ref.watch(currentAssetNotifier);
|
||||
@@ -217,7 +279,10 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||
color: context.textTheme.bodyMedium?.color?.withAlpha(155),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 64),
|
||||
// Appears in (Albums)
|
||||
_buildAppearsInList(ref, context),
|
||||
// padding at the bottom to avoid cut-off
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
@@ -163,6 +164,7 @@ class _AssetTileWidget extends ConsumerWidget {
|
||||
initialIndex: assetIndex,
|
||||
timelineService: ref.read(timelineServiceProvider),
|
||||
heroOffset: heroOffset,
|
||||
currentAlbum: ref.read(currentRemoteAlbumProvider),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user