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:
@@ -14,10 +14,7 @@ import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||
class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
||||
final Album album;
|
||||
|
||||
const AlbumAdditionalSharedUserSelectionPage({
|
||||
super.key,
|
||||
required this.album,
|
||||
});
|
||||
const AlbumAdditionalSharedUserSelectionPage({super.key, required this.album});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -30,17 +27,9 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
||||
|
||||
buildTileIcon(UserDto user) {
|
||||
if (sharedUsersList.value.contains(user)) {
|
||||
return CircleAvatar(
|
||||
backgroundColor: context.primaryColor,
|
||||
child: const Icon(
|
||||
Icons.check_rounded,
|
||||
size: 25,
|
||||
),
|
||||
);
|
||||
return CircleAvatar(backgroundColor: context.primaryColor, child: const Icon(Icons.check_rounded, size: 25));
|
||||
} else {
|
||||
return UserCircleAvatar(
|
||||
user: user,
|
||||
);
|
||||
return UserCircleAvatar(user: user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,31 +42,19 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Chip(
|
||||
backgroundColor: context.primaryColor.withValues(alpha: 0.15),
|
||||
label: Text(
|
||||
user.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
label: Text(user.name, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView(
|
||||
children: [
|
||||
Wrap(
|
||||
children: [...usersChip],
|
||||
),
|
||||
Wrap(children: [...usersChip]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
'suggestions'.tr(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
ListView.builder(
|
||||
@@ -87,31 +64,15 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
||||
return ListTile(
|
||||
leading: buildTileIcon(users[index]),
|
||||
dense: true,
|
||||
title: Text(
|
||||
users[index].name,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
users[index].email,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
title: Text(users[index].name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
subtitle: Text(users[index].email, style: const TextStyle(fontSize: 12)),
|
||||
onTap: () {
|
||||
if (sharedUsersList.value.contains(users[index])) {
|
||||
sharedUsersList.value = sharedUsersList.value
|
||||
.where(
|
||||
(selectedUser) => selectedUser.id != users[index].id,
|
||||
)
|
||||
.where((selectedUser) => selectedUser.id != users[index].id)
|
||||
.toSet();
|
||||
} else {
|
||||
sharedUsersList.value = {
|
||||
...sharedUsersList.value,
|
||||
users[index],
|
||||
};
|
||||
sharedUsersList.value = {...sharedUsersList.value, users[index]};
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -124,9 +85,7 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'invite_to_album',
|
||||
).tr(),
|
||||
title: const Text('invite_to_album').tr(),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
leading: IconButton(
|
||||
@@ -138,19 +97,14 @@ class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: sharedUsersList.value.isEmpty ? null : addNewUsersHandler,
|
||||
child: const Text(
|
||||
"add",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
child: const Text("add", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: suggestedShareUsers.widgetWhen(
|
||||
onData: (users) {
|
||||
for (var sharedUsers in album.sharedUsers) {
|
||||
users.removeWhere(
|
||||
(u) => u.id == sharedUsers.id || u.id == album.ownerId,
|
||||
);
|
||||
users.removeWhere((u) => u.id == sharedUsers.id || u.id == album.ownerId);
|
||||
}
|
||||
|
||||
return buildUserList(users);
|
||||
|
||||
@@ -13,11 +13,7 @@ import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AlbumAssetSelectionPage extends HookConsumerWidget {
|
||||
const AlbumAssetSelectionPage({
|
||||
super.key,
|
||||
required this.existingAssets,
|
||||
this.canDeselect = false,
|
||||
});
|
||||
const AlbumAssetSelectionPage({super.key, required this.existingAssets, this.canDeselect = false});
|
||||
|
||||
final Set<Asset> existingAssets;
|
||||
final bool canDeselect;
|
||||
@@ -52,10 +48,7 @@ class AlbumAssetSelectionPage extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
title: selected.value.isEmpty
|
||||
? const Text(
|
||||
'add_photos',
|
||||
style: TextStyle(fontSize: 18),
|
||||
).tr()
|
||||
? const Text('add_photos', style: TextStyle(fontSize: 18)).tr()
|
||||
: const Text(
|
||||
'share_assets_selected',
|
||||
style: TextStyle(fontSize: 18),
|
||||
@@ -70,17 +63,12 @@ class AlbumAssetSelectionPage extends HookConsumerWidget {
|
||||
},
|
||||
child: Text(
|
||||
canDeselect ? "done" : "add",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.bold, color: context.primaryColor),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: assetSelectionRenderList.widgetWhen(
|
||||
onData: (data) => buildBody(data),
|
||||
),
|
||||
body: assetSelectionRenderList.widgetWhen(onData: (data) => buildBody(data)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,7 @@ class AlbumControlButton extends ConsumerWidget {
|
||||
final void Function()? onAddPhotosPressed;
|
||||
final void Function()? onAddUsersPressed;
|
||||
|
||||
const AlbumControlButton({
|
||||
super.key,
|
||||
this.onAddPhotosPressed,
|
||||
this.onAddUsersPressed,
|
||||
});
|
||||
const AlbumControlButton({super.key, this.onAddPhotosPressed, this.onAddUsersPressed});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
|
||||
@@ -33,9 +33,7 @@ class AlbumDateRange extends ConsumerWidget {
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: Text(
|
||||
_getDateRangeText(startDate, endDate),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceVariant),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -46,8 +44,9 @@ class AlbumDateRange extends ConsumerWidget {
|
||||
return DateFormat.yMMMd().format(startDate);
|
||||
}
|
||||
|
||||
final String startDateText =
|
||||
(startDate.year == endDate.year ? DateFormat.MMMd() : DateFormat.yMMMd()).format(startDate);
|
||||
final String startDateText = (startDate.year == endDate.year ? DateFormat.MMMd() : DateFormat.yMMMd()).format(
|
||||
startDate,
|
||||
);
|
||||
final String endDateText = DateFormat.yMMMd().format(endDate);
|
||||
return "$startDateText - $endDateText";
|
||||
}
|
||||
|
||||
@@ -36,10 +36,7 @@ class AlbumDescription extends ConsumerWidget {
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||
child: Text(
|
||||
albumDescription ?? 'add_a_description'.tr(),
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
child: Text(albumDescription ?? 'add_a_description'.tr(), style: context.textTheme.bodyLarge),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
||||
final isSuccess = await ref.read(albumProvider.notifier).leaveAlbum(album);
|
||||
|
||||
if (isSuccess) {
|
||||
context.navigateTo(
|
||||
const TabControllerRoute(children: [AlbumsRoute()]),
|
||||
);
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
} else {
|
||||
showErrorMessage();
|
||||
}
|
||||
@@ -110,10 +108,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [...actions],
|
||||
),
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [...actions]),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -123,20 +118,9 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
||||
buildOwnerInfo() {
|
||||
return ListTile(
|
||||
leading: owner != null ? UserCircleAvatar(user: owner.toDto()) : const SizedBox(),
|
||||
title: Text(
|
||||
album.owner.value?.name ?? "",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
album.owner.value?.email ?? "",
|
||||
style: TextStyle(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
trailing: Text(
|
||||
"owner",
|
||||
style: context.textTheme.labelLarge,
|
||||
).tr(),
|
||||
title: Text(album.owner.value?.name ?? "", style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
subtitle: Text(album.owner.value?.email ?? "", style: TextStyle(color: context.colorScheme.onSurfaceSecondary)),
|
||||
trailing: Text("owner", style: context.textTheme.labelLarge).tr(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,22 +132,9 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
||||
itemBuilder: (context, index) {
|
||||
final user = sharedUsers.value[index];
|
||||
return ListTile(
|
||||
leading: UserCircleAvatar(
|
||||
user: user,
|
||||
radius: 22,
|
||||
),
|
||||
title: Text(
|
||||
user.name,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
user.email,
|
||||
style: TextStyle(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
),
|
||||
leading: UserCircleAvatar(user: user, radius: 22),
|
||||
title: Text(user.name, style: const TextStyle(fontWeight: FontWeight.w500)),
|
||||
subtitle: Text(user.email, style: TextStyle(color: context.colorScheme.onSurfaceSecondary)),
|
||||
trailing: userId == user.id || isOwner ? const Icon(Icons.more_horiz_rounded) : const SizedBox(),
|
||||
onTap: userId == user.id || isOwner ? () => handleUserClick(user) : null,
|
||||
);
|
||||
@@ -206,9 +177,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
|
||||
).tr(),
|
||||
subtitle: Text(
|
||||
"let_others_respond",
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
).tr(),
|
||||
),
|
||||
buildSectionTitle("shared_album_section_people_title".tr()),
|
||||
|
||||
@@ -41,11 +41,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget {
|
||||
itemBuilder: ((context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: UserCircleAvatar(
|
||||
user: sharedUsers.value[index],
|
||||
radius: 18,
|
||||
size: 36,
|
||||
),
|
||||
child: UserCircleAvatar(user: sharedUsers.value[index], radius: 18, size: 36),
|
||||
);
|
||||
}),
|
||||
itemCount: sharedUsers.value.length,
|
||||
|
||||
@@ -25,10 +25,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
final suggestedShareUsers = ref.watch(otherUsersProvider);
|
||||
|
||||
createSharedAlbum() async {
|
||||
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
|
||||
ref.watch(albumTitleProvider),
|
||||
assets,
|
||||
);
|
||||
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(ref.watch(albumTitleProvider), assets);
|
||||
|
||||
if (newAlbum != null) {
|
||||
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
|
||||
@@ -40,9 +37,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
child: SnackBar(
|
||||
content: Text(
|
||||
'select_user_for_sharing_page_err_album',
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
).tr(),
|
||||
),
|
||||
);
|
||||
@@ -50,17 +45,9 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
|
||||
buildTileIcon(UserDto user) {
|
||||
if (sharedUsersList.value.contains(user)) {
|
||||
return CircleAvatar(
|
||||
backgroundColor: context.primaryColor,
|
||||
child: const Icon(
|
||||
Icons.check_rounded,
|
||||
size: 25,
|
||||
),
|
||||
);
|
||||
return CircleAvatar(backgroundColor: context.primaryColor, child: const Icon(Icons.check_rounded, size: 25));
|
||||
} else {
|
||||
return UserCircleAvatar(
|
||||
user: user,
|
||||
);
|
||||
return UserCircleAvatar(user: user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,11 +62,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
backgroundColor: context.primaryColor.withValues(alpha: 0.15),
|
||||
label: Text(
|
||||
user.email,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.black87,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black87, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -87,18 +70,12 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
}
|
||||
return ListView(
|
||||
children: [
|
||||
Wrap(
|
||||
children: [...usersChip],
|
||||
),
|
||||
Wrap(children: [...usersChip]),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: const Text(
|
||||
'suggestions',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
ListView.builder(
|
||||
@@ -107,25 +84,14 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
itemBuilder: ((context, index) {
|
||||
return ListTile(
|
||||
leading: buildTileIcon(users[index]),
|
||||
title: Text(
|
||||
users[index].email,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
title: Text(users[index].email, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
onTap: () {
|
||||
if (sharedUsersList.value.contains(users[index])) {
|
||||
sharedUsersList.value = sharedUsersList.value
|
||||
.where(
|
||||
(selectedUser) => selectedUser.id != users[index].id,
|
||||
)
|
||||
.where((selectedUser) => selectedUser.id != users[index].id)
|
||||
.toSet();
|
||||
} else {
|
||||
sharedUsersList.value = {
|
||||
...sharedUsersList.value,
|
||||
users[index],
|
||||
};
|
||||
sharedUsersList.value = {...sharedUsersList.value, users[index]};
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -138,10 +104,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'invite_to_album',
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
).tr(),
|
||||
title: Text('invite_to_album', style: TextStyle(color: context.primaryColor)).tr(),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
leading: IconButton(
|
||||
@@ -152,9 +115,7 @@ class AlbumSharedUserSelectionPage extends HookConsumerWidget {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: context.primaryColor,
|
||||
),
|
||||
style: TextButton.styleFrom(foregroundColor: context.primaryColor),
|
||||
onPressed: sharedUsersList.value.isEmpty ? null : createSharedAlbum,
|
||||
child: const Text(
|
||||
"create_album",
|
||||
|
||||
@@ -19,32 +19,20 @@ class AlbumTitle extends ConsumerWidget {
|
||||
return const (false, false, '');
|
||||
}
|
||||
|
||||
return (
|
||||
album.ownerId == userId,
|
||||
album.isRemote,
|
||||
album.name,
|
||||
);
|
||||
return (album.ownerId == userId, album.isRemote, album.name);
|
||||
}),
|
||||
);
|
||||
|
||||
if (isOwner && isRemote) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||
child: AlbumViewerEditableTitle(
|
||||
albumName: albumName,
|
||||
titleFocusNode: titleFocusNode,
|
||||
),
|
||||
child: AlbumViewerEditableTitle(albumName: albumName, titleFocusNode: titleFocusNode),
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||
child: Text(
|
||||
albumName,
|
||||
style: context.textTheme.headlineLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
child: Text(albumName, style: context.textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.w700)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,10 +65,7 @@ class AlbumViewer extends HookConsumerWidget {
|
||||
/// If they exist, add to selected asset state to show they are already selected.
|
||||
void onAddPhotosPressed() async {
|
||||
AssetSelectionPageResult? returnPayload = await context.pushRoute<AssetSelectionPageResult?>(
|
||||
AlbumAssetSelectionRoute(
|
||||
existingAssets: album.assets,
|
||||
canDeselect: false,
|
||||
),
|
||||
AlbumAssetSelectionRoute(existingAssets: album.assets, canDeselect: false),
|
||||
);
|
||||
|
||||
if (returnPayload != null && returnPayload.selectedAssets.isNotEmpty) {
|
||||
@@ -98,9 +95,7 @@ class AlbumViewer extends HookConsumerWidget {
|
||||
onActivitiesPressed() {
|
||||
if (album.remoteId != null) {
|
||||
ref.read(currentAssetProvider.notifier).set(null);
|
||||
context.pushRoute(
|
||||
const ActivitiesRoute(),
|
||||
);
|
||||
context.pushRoute(const ActivitiesRoute());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,14 +124,8 @@ class AlbumViewer extends HookConsumerWidget {
|
||||
children: [
|
||||
const SizedBox(height: 32),
|
||||
const AlbumDateRange(),
|
||||
AlbumTitle(
|
||||
key: const ValueKey("albumTitle"),
|
||||
titleFocusNode: titleFocusNode,
|
||||
),
|
||||
AlbumDescription(
|
||||
key: const ValueKey("albumDescription"),
|
||||
descriptionFocusNode: descriptionFocusNode,
|
||||
),
|
||||
AlbumTitle(key: const ValueKey("albumTitle"), titleFocusNode: titleFocusNode),
|
||||
AlbumDescription(key: const ValueKey("albumDescription"), descriptionFocusNode: descriptionFocusNode),
|
||||
const AlbumSharedUserIcons(),
|
||||
if (album.isRemote)
|
||||
Padding(
|
||||
|
||||
@@ -21,9 +21,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
ref.listen(assetSelectionTimelineProvider, (_, __) {});
|
||||
|
||||
ref.listen(albumWatcher(albumId), (_, albumFuture) {
|
||||
albumFuture.whenData(
|
||||
(value) => ref.read(currentAlbumProvider.notifier).set(value),
|
||||
);
|
||||
albumFuture.whenData((value) => ref.read(currentAlbumProvider.notifier).set(value));
|
||||
});
|
||||
|
||||
return const Scaffold(body: AlbumViewer());
|
||||
|
||||
@@ -52,21 +52,18 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
filterMode.value = mode;
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
searchController.addListener(() {
|
||||
useEffect(() {
|
||||
searchController.addListener(() {
|
||||
onSearch(searchController.text, filterMode.value);
|
||||
});
|
||||
|
||||
return () {
|
||||
searchController.removeListener(() {
|
||||
onSearch(searchController.text, filterMode.value);
|
||||
});
|
||||
|
||||
return () {
|
||||
searchController.removeListener(() {
|
||||
onSearch(searchController.text, filterMode.value);
|
||||
});
|
||||
debounceTimer.value?.cancel();
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
debounceTimer.value?.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
clearSearch() {
|
||||
filterMode.value = QuickFilterMode.all;
|
||||
@@ -79,13 +76,8 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
showUploadButton: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.add_rounded,
|
||||
size: 28,
|
||||
),
|
||||
onPressed: () => context.pushRoute(
|
||||
CreateAlbumRoute(),
|
||||
),
|
||||
icon: const Icon(Icons.add_rounded, size: 28),
|
||||
onPressed: () => context.pushRoute(CreateAlbumRoute()),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -100,13 +92,8 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.colorScheme.onSurface.withAlpha(0),
|
||||
width: 0,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
border: Border.all(color: context.colorScheme.onSurface.withAlpha(0), width: 0),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withValues(alpha: 0.075),
|
||||
@@ -124,10 +111,7 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
hintText: 'search_albums'.tr(),
|
||||
prefixIcon: const Icon(Icons.search_rounded),
|
||||
suffixIcon: searchController.text.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear_rounded),
|
||||
onPressed: clearSearch,
|
||||
)
|
||||
? IconButton(icon: const Icon(Icons.clear_rounded), onPressed: clearSearch)
|
||||
: null,
|
||||
controller: searchController,
|
||||
onChanged: (_) => onSearch(searchController.text, filterMode.value),
|
||||
@@ -153,10 +137,7 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
isSelected: filterMode.value == QuickFilterMode.sharedWithMe,
|
||||
onTap: () {
|
||||
changeFilter(QuickFilterMode.sharedWithMe);
|
||||
onSearch(
|
||||
searchController.text,
|
||||
QuickFilterMode.sharedWithMe,
|
||||
);
|
||||
onSearch(searchController.text, QuickFilterMode.sharedWithMe);
|
||||
},
|
||||
),
|
||||
QuickFilterButton(
|
||||
@@ -164,10 +145,7 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
isSelected: filterMode.value == QuickFilterMode.myAlbums,
|
||||
onTap: () {
|
||||
changeFilter(QuickFilterMode.myAlbums);
|
||||
onSearch(
|
||||
searchController.text,
|
||||
QuickFilterMode.myAlbums,
|
||||
);
|
||||
onSearch(searchController.text, QuickFilterMode.myAlbums);
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -177,10 +155,7 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
children: [
|
||||
const SortButton(),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isGrid.value ? Icons.view_list_outlined : Icons.grid_view_outlined,
|
||||
size: 24,
|
||||
),
|
||||
icon: Icon(isGrid.value ? Icons.view_list_outlined : Icons.grid_view_outlined, size: 24),
|
||||
onPressed: toggleViewMode,
|
||||
),
|
||||
],
|
||||
@@ -201,9 +176,7 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
itemBuilder: (context, index) {
|
||||
return AlbumThumbnailCard(
|
||||
album: sorted[index],
|
||||
onTap: () => context.pushRoute(
|
||||
AlbumViewerRoute(albumId: sorted[index].id),
|
||||
),
|
||||
onTap: () => context.pushRoute(AlbumViewerRoute(albumId: sorted[index].id)),
|
||||
showOwner: true,
|
||||
);
|
||||
},
|
||||
@@ -221,44 +194,22 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
sorted[index].name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: sorted[index].ownerId != null
|
||||
? Text(
|
||||
'${'items_count'.t(
|
||||
context: context,
|
||||
args: {
|
||||
'count': sorted[index].assetCount,
|
||||
},
|
||||
)} • ${sorted[index].ownerId != userId ? 'shared_by_user'.t(
|
||||
context: context,
|
||||
args: {
|
||||
'user': sorted[index].ownerName!,
|
||||
},
|
||||
) : 'owned'.t(context: context)}',
|
||||
'${'items_count'.t(context: context, args: {'count': sorted[index].assetCount})} • ${sorted[index].ownerId != userId ? 'shared_by_user'.t(context: context, args: {'user': sorted[index].ownerName!}) : 'owned'.t(context: context)}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
onTap: () => context.pushRoute(
|
||||
AlbumViewerRoute(albumId: sorted[index].id),
|
||||
),
|
||||
leadingPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
),
|
||||
onTap: () => context.pushRoute(AlbumViewerRoute(albumId: sorted[index].id)),
|
||||
leadingPadding: const EdgeInsets.only(right: 16),
|
||||
leading: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15),
|
||||
),
|
||||
child: ImmichThumbnail(
|
||||
asset: sorted[index].thumbnail.value,
|
||||
width: 80,
|
||||
height: 80,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
child: ImmichThumbnail(asset: sorted[index].thumbnail.value, width: 80, height: 80),
|
||||
),
|
||||
// minVerticalPadding: 1,
|
||||
),
|
||||
@@ -275,12 +226,7 @@ class AlbumsPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
class QuickFilterButton extends StatelessWidget {
|
||||
const QuickFilterButton({
|
||||
super.key,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
required this.label,
|
||||
});
|
||||
const QuickFilterButton({super.key, required this.isSelected, required this.onTap, required this.label});
|
||||
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
@@ -291,18 +237,11 @@ class QuickFilterButton extends StatelessWidget {
|
||||
return TextButton(
|
||||
onPressed: onTap,
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
isSelected ? context.colorScheme.primary : Colors.transparent,
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.all(isSelected ? context.colorScheme.primary : Colors.transparent),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: context.colorScheme.onSurface.withAlpha(25),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
side: BorderSide(color: context.colorScheme.onSurface.withAlpha(25), width: 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -329,15 +268,9 @@ class SortButton extends ConsumerWidget {
|
||||
style: MenuStyle(
|
||||
elevation: const WidgetStatePropertyAll(1),
|
||||
shape: WidgetStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.all(4),
|
||||
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.all(4)),
|
||||
),
|
||||
consumeOutsideTap: true,
|
||||
menuChildren: AlbumSortMode.values
|
||||
@@ -345,16 +278,18 @@ class SortButton extends ConsumerWidget {
|
||||
(mode) => MenuItemButton(
|
||||
leadingIcon: albumSortOption == mode
|
||||
? albumSortIsReverse
|
||||
? Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color:
|
||||
albumSortOption == mode ? context.colorScheme.onPrimary : context.colorScheme.onSurface,
|
||||
)
|
||||
: Icon(
|
||||
Icons.keyboard_arrow_up_rounded,
|
||||
color:
|
||||
albumSortOption == mode ? context.colorScheme.onPrimary : context.colorScheme.onSurface,
|
||||
)
|
||||
? Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: albumSortOption == mode
|
||||
? context.colorScheme.onPrimary
|
||||
: context.colorScheme.onSurface,
|
||||
)
|
||||
: Icon(
|
||||
Icons.keyboard_arrow_up_rounded,
|
||||
color: albumSortOption == mode
|
||||
? context.colorScheme.onPrimary
|
||||
: context.colorScheme.onSurface,
|
||||
)
|
||||
: const Icon(Icons.abc, color: Colors.transparent),
|
||||
onPressed: () {
|
||||
final selected = albumSortOption == mode;
|
||||
@@ -366,18 +301,12 @@ class SortButton extends ConsumerWidget {
|
||||
}
|
||||
},
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(
|
||||
const EdgeInsets.fromLTRB(16, 16, 32, 16),
|
||||
),
|
||||
padding: WidgetStateProperty.all(const EdgeInsets.fromLTRB(16, 16, 32, 16)),
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
albumSortOption == mode ? context.colorScheme.primary : Colors.transparent,
|
||||
),
|
||||
shape: WidgetStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
),
|
||||
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
|
||||
@@ -22,23 +22,17 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
||||
assets.value = await ref.read(albumMediaRepositoryProvider).getAssets(album.localId!);
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
getAssetsInAlbum();
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
getAssetsInAlbum();
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: Column(
|
||||
children: [
|
||||
Text(
|
||||
album.name,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(album.name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text(
|
||||
@@ -52,10 +46,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_new_rounded)),
|
||||
),
|
||||
body: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
@@ -65,11 +56,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
||||
),
|
||||
itemCount: assets.value.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ImmichThumbnail(
|
||||
asset: assets.value[index],
|
||||
width: 100,
|
||||
height: 100,
|
||||
);
|
||||
return ImmichThumbnail(asset: assets.value[index], width: 100, height: 100);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -23,45 +23,29 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
final albums = ref.watch(backupProvider).availableAlbums;
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
buildAlbumSelectionList() {
|
||||
if (albums.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
((context, index) {
|
||||
return AlbumInfoListTile(
|
||||
album: albums[index],
|
||||
);
|
||||
}),
|
||||
childCount: albums.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(((context, index) {
|
||||
return AlbumInfoListTile(album: albums[index]);
|
||||
}), childCount: albums.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
buildAlbumSelectionGrid() {
|
||||
if (albums.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
return SliverPadding(
|
||||
@@ -74,9 +58,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
),
|
||||
itemCount: albums.length,
|
||||
itemBuilder: ((context, index) {
|
||||
return AlbumInfoCard(
|
||||
album: albums[index],
|
||||
);
|
||||
return AlbumInfoCard(album: albums[index]);
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -101,10 +83,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
),
|
||||
backgroundColor: context.primaryColor,
|
||||
deleteIconColor: isDarkTheme ? Colors.black : Colors.white,
|
||||
deleteIcon: const Icon(
|
||||
Icons.cancel_rounded,
|
||||
size: 15,
|
||||
),
|
||||
deleteIcon: const Icon(Icons.cancel_rounded, size: 15),
|
||||
onDeleted: removeSelection,
|
||||
),
|
||||
),
|
||||
@@ -125,18 +104,11 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
child: Chip(
|
||||
label: Text(
|
||||
album.name,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: context.scaffoldBackgroundColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 12, color: context.scaffoldBackgroundColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
backgroundColor: Colors.red[300],
|
||||
deleteIconColor: context.scaffoldBackgroundColor,
|
||||
deleteIcon: const Icon(
|
||||
Icons.cancel_rounded,
|
||||
size: 15,
|
||||
),
|
||||
deleteIcon: const Icon(Icons.cancel_rounded, size: 15),
|
||||
onDeleted: removeSelection,
|
||||
),
|
||||
),
|
||||
@@ -155,13 +127,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
title: const Text(
|
||||
"backup_album_selection_page_select_albums",
|
||||
).tr(),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
title: const Text("backup_album_selection_page_select_albums").tr(),
|
||||
elevation: 0,
|
||||
),
|
||||
body: CustomScrollView(
|
||||
@@ -172,25 +139,14 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: Text(
|
||||
"backup_album_selection_page_selection_info",
|
||||
style: context.textTheme.titleSmall,
|
||||
).tr(),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||
child: Text("backup_album_selection_page_selection_info", style: context.textTheme.titleSmall).tr(),
|
||||
),
|
||||
// Selected Album Chips
|
||||
|
||||
// Selected Album Chips
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Wrap(
|
||||
children: [
|
||||
...buildSelectedAlbumNameChip(),
|
||||
...buildExcludedAlbumNameChip(),
|
||||
],
|
||||
),
|
||||
child: Wrap(children: [...buildSelectedAlbumNameChip(), ...buildExcludedAlbumNameChip()]),
|
||||
),
|
||||
|
||||
SettingsSwitchListTile(
|
||||
@@ -198,21 +154,15 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
title: "sync_albums".tr(),
|
||||
subtitle: "sync_upload_album_setting_subtitle".tr(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
titleStyle: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
subtitleStyle: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
titleStyle: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
subtitleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary),
|
||||
onChanged: handleSyncAlbumToggle,
|
||||
),
|
||||
|
||||
ListTile(
|
||||
title: Text(
|
||||
"backup_album_selection_page_albums_device".tr(
|
||||
namedArgs: {
|
||||
'count': ref.watch(backupProvider).availableAlbums.length.toString(),
|
||||
},
|
||||
namedArgs: {'count': ref.watch(backupProvider).availableAlbums.length.toString()},
|
||||
),
|
||||
style: context.textTheme.titleSmall,
|
||||
),
|
||||
@@ -220,46 +170,30 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(
|
||||
"backup_album_selection_page_albums_tap",
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor),
|
||||
).tr(),
|
||||
),
|
||||
trailing: IconButton(
|
||||
splashRadius: 16,
|
||||
icon: Icon(
|
||||
Icons.info,
|
||||
size: 20,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.info, size: 20, color: context.primaryColor),
|
||||
onPressed: () {
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10),
|
||||
),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
elevation: 5,
|
||||
title: Text(
|
||||
'backup_album_selection_page_selection_info',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: context.primaryColor),
|
||||
).tr(),
|
||||
content: SingleChildScrollView(
|
||||
child: ListBody(
|
||||
children: [
|
||||
const Text(
|
||||
'backup_album_selection_page_assets_scatter',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
style: TextStyle(fontSize: 14),
|
||||
).tr(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -31,52 +31,44 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
final didGetBackupInfo = useState(false);
|
||||
|
||||
bool hasExclusiveAccess = backupState.backupProgress != BackUpProgressEnum.inBackground;
|
||||
bool shouldBackup = backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length == 0 ||
|
||||
bool shouldBackup =
|
||||
backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length == 0 ||
|
||||
!hasExclusiveAccess
|
||||
? false
|
||||
: true;
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
// Update the background settings information just to make sure we
|
||||
// have the latest, since the platform channel will not update
|
||||
// automatically
|
||||
if (Platform.isIOS) {
|
||||
ref.watch(iOSBackgroundSettingsProvider.notifier).refresh();
|
||||
}
|
||||
useEffect(() {
|
||||
// Update the background settings information just to make sure we
|
||||
// have the latest, since the platform channel will not update
|
||||
// automatically
|
||||
if (Platform.isIOS) {
|
||||
ref.watch(iOSBackgroundSettingsProvider.notifier).refresh();
|
||||
}
|
||||
|
||||
ref.watch(websocketProvider.notifier).stopListenToEvent('on_upload_success');
|
||||
ref.watch(websocketProvider.notifier).stopListenToEvent('on_upload_success');
|
||||
|
||||
return () {
|
||||
WakelockPlus.disable();
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
return () {
|
||||
WakelockPlus.disable();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (backupState.backupProgress == BackUpProgressEnum.idle && !didGetBackupInfo.value) {
|
||||
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||
didGetBackupInfo.value = true;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[backupState.backupProgress],
|
||||
);
|
||||
useEffect(() {
|
||||
if (backupState.backupProgress == BackUpProgressEnum.idle && !didGetBackupInfo.value) {
|
||||
ref.watch(backupProvider.notifier).getBackupInfo();
|
||||
didGetBackupInfo.value = true;
|
||||
}
|
||||
return null;
|
||||
}, [backupState.backupProgress]);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
|
||||
WakelockPlus.enable();
|
||||
} else {
|
||||
WakelockPlus.disable();
|
||||
}
|
||||
useEffect(() {
|
||||
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
|
||||
WakelockPlus.enable();
|
||||
} else {
|
||||
WakelockPlus.disable();
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[backupState.backupProgress],
|
||||
);
|
||||
return null;
|
||||
}, [backupState.backupProgress]);
|
||||
|
||||
Widget buildSelectedAlbumName() {
|
||||
var text = "backup_controller_page_backup_selected".tr();
|
||||
@@ -95,9 +87,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
text.trim().substring(0, text.length - 2),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -105,9 +95,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
"backup_controller_page_none_selected".tr(),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -126,9 +114,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
text.trim().substring(0, text.length - 2),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.red[300],
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: Colors.red[300]),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -141,22 +127,14 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: context.colorScheme.outlineVariant,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
side: BorderSide(color: context.colorScheme.outlineVariant, width: 1),
|
||||
),
|
||||
elevation: 0,
|
||||
borderOnForeground: false,
|
||||
child: ListTile(
|
||||
minVerticalPadding: 18,
|
||||
title: Text(
|
||||
"backup_controller_page_albums",
|
||||
style: context.textTheme.titleMedium,
|
||||
).tr(),
|
||||
title: Text("backup_controller_page_albums", style: context.textTheme.titleMedium).tr(),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Column(
|
||||
@@ -164,9 +142,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
"backup_controller_page_to_backup",
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
).tr(),
|
||||
buildSelectedAlbumName(),
|
||||
buildExcludedAlbumName(),
|
||||
@@ -181,12 +157,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
// waited until backup albums are stored in DB
|
||||
ref.read(albumProvider.notifier).refreshDeviceAlbums();
|
||||
},
|
||||
child: const Text(
|
||||
"select",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -202,11 +173,10 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
|
||||
Widget buildBackupButton() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
),
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: Container(
|
||||
child: backupState.backupProgress == BackUpProgressEnum.inProgress ||
|
||||
child:
|
||||
backupState.backupProgress == BackUpProgressEnum.inProgress ||
|
||||
backupState.backupProgress == BackUpProgressEnum.manualInProgress
|
||||
? ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
@@ -221,22 +191,13 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
ref.read(backupProvider.notifier).cancelBackup();
|
||||
}
|
||||
},
|
||||
child: const Text(
|
||||
"cancel",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
child: const Text("cancel", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: shouldBackup ? startBackup : null,
|
||||
child: const Text(
|
||||
"backup_controller_page_start_backup",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
@@ -246,36 +207,28 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
buildBackgroundBackupInfo() {
|
||||
return const ListTile(
|
||||
leading: Icon(Icons.info_outline_rounded),
|
||||
title: Text(
|
||||
"Background backup is currently running, cannot start manual backup",
|
||||
),
|
||||
title: Text("Background backup is currently running, cannot start manual backup"),
|
||||
);
|
||||
}
|
||||
|
||||
buildLoadingIndicator() {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.only(top: 42.0),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: const Text(
|
||||
"backup_controller_page_backup",
|
||||
).tr(),
|
||||
title: const Text("backup_controller_page_backup").tr(),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
ref.watch(websocketProvider.notifier).listenUploadEvent();
|
||||
context.maybePop(true);
|
||||
},
|
||||
splashRadius: 24,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
@@ -283,9 +236,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
child: IconButton(
|
||||
onPressed: () => context.pushRoute(const BackupOptionsRoute()),
|
||||
splashRadius: 24,
|
||||
icon: const Icon(
|
||||
Icons.settings_outlined,
|
||||
),
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -325,10 +276,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
|
||||
buildBackupButton(),
|
||||
]
|
||||
: [
|
||||
buildFolderSelectionTile(),
|
||||
if (!didGetBackupInfo.value) buildLoadingIndicator(),
|
||||
],
|
||||
: [buildFolderSelectionTile(), if (!didGetBackupInfo.value) buildLoadingIndicator()],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -15,9 +15,7 @@ class BackupOptionsPage extends StatelessWidget {
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(true),
|
||||
splashRadius: 24,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: const BackupSettings(),
|
||||
|
||||
@@ -51,35 +51,25 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final selectedAlbum = ref
|
||||
.watch(backupAlbumProvider)
|
||||
.where(
|
||||
(album) => album.backupSelection == BackupSelection.selected,
|
||||
)
|
||||
.where((album) => album.backupSelection == BackupSelection.selected)
|
||||
.toList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: Text(
|
||||
"backup_controller_page_backup".t(),
|
||||
),
|
||||
title: Text("backup_controller_page_backup".t()),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
context.maybePop(true);
|
||||
},
|
||||
splashRadius: 24,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16.0,
|
||||
right: 16,
|
||||
bottom: 32,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32),
|
||||
child: ListView(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
@@ -89,15 +79,10 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
||||
const _BackupCard(),
|
||||
const _RemainderCard(),
|
||||
const Divider(),
|
||||
BackupToggleButton(
|
||||
onStart: () async => await startBackup(),
|
||||
onStop: () async => await stopBackup(),
|
||||
),
|
||||
BackupToggleButton(onStart: () async => await startBackup(), onStop: () async => await stopBackup()),
|
||||
TextButton.icon(
|
||||
icon: const Icon(Icons.info_outline_rounded),
|
||||
onPressed: () => context.pushRoute(
|
||||
const DriftUploadDetailRoute(),
|
||||
),
|
||||
onPressed: () => context.pushRoute(const DriftUploadDetailRoute()),
|
||||
label: Text("view_details".t(context: context)),
|
||||
),
|
||||
],
|
||||
@@ -119,9 +104,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
String text = "backup_controller_page_backup_selected".tr();
|
||||
final albums = ref
|
||||
.watch(backupAlbumProvider)
|
||||
.where(
|
||||
(album) => album.backupSelection == BackupSelection.selected,
|
||||
)
|
||||
.where((album) => album.backupSelection == BackupSelection.selected)
|
||||
.toList();
|
||||
|
||||
if (albums.isNotEmpty) {
|
||||
@@ -137,9 +120,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
text.trim().substring(0, text.length - 2),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -147,9 +128,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
"backup_controller_page_none_selected".tr(),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -159,9 +138,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
String text = "backup_controller_page_excluded".tr();
|
||||
final albums = ref
|
||||
.watch(backupAlbumProvider)
|
||||
.where(
|
||||
(album) => album.backupSelection == BackupSelection.excluded,
|
||||
)
|
||||
.where((album) => album.backupSelection == BackupSelection.excluded)
|
||||
.toList();
|
||||
|
||||
if (albums.isNotEmpty) {
|
||||
@@ -173,9 +150,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
text.trim().substring(0, text.length - 2),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.red[300],
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: Colors.red[300]),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -186,19 +161,13 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
side: BorderSide(
|
||||
color: context.colorScheme.outlineVariant,
|
||||
width: 1,
|
||||
),
|
||||
side: BorderSide(color: context.colorScheme.outlineVariant, width: 1),
|
||||
),
|
||||
elevation: 0,
|
||||
borderOnForeground: false,
|
||||
child: ListTile(
|
||||
minVerticalPadding: 18,
|
||||
title: Text(
|
||||
"backup_controller_page_albums",
|
||||
style: context.textTheme.titleMedium,
|
||||
).tr(),
|
||||
title: Text("backup_controller_page_albums", style: context.textTheme.titleMedium).tr(),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Column(
|
||||
@@ -206,9 +175,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
"backup_controller_page_to_backup",
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
).tr(),
|
||||
buildSelectedAlbumName(),
|
||||
buildExcludedAlbumName(),
|
||||
@@ -224,12 +191,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||
}
|
||||
ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||
},
|
||||
child: const Text(
|
||||
"select",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -119,9 +119,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
focusNode: _searchFocusNode,
|
||||
onChanged: (value) => setState(() => _searchQuery = value.trim()),
|
||||
)
|
||||
: const Text(
|
||||
"backup_album_selection_page_select_albums",
|
||||
).t(context: context),
|
||||
: const Text("backup_album_selection_page_select_albums").t(context: context),
|
||||
actions: [
|
||||
if (!_isSearchMode)
|
||||
IconButton(
|
||||
@@ -151,27 +149,20 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
|
||||
child: Text(
|
||||
"backup_album_selection_page_selection_info",
|
||||
style: context.textTheme.titleSmall,
|
||||
).t(context: context),
|
||||
),
|
||||
// Selected Album Chips
|
||||
|
||||
// Selected Album Chips
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Wrap(
|
||||
children: [
|
||||
_SelectedAlbumNameChips(
|
||||
selectedBackupAlbums: selectedBackupAlbums,
|
||||
),
|
||||
_ExcludedAlbumNameChips(
|
||||
excludedBackupAlbums: excludedBackupAlbums,
|
||||
),
|
||||
_SelectedAlbumNameChips(selectedBackupAlbums: selectedBackupAlbums),
|
||||
_ExcludedAlbumNameChips(excludedBackupAlbums: excludedBackupAlbums),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -181,48 +172,33 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
title: "sync_albums".t(context: context),
|
||||
subtitle: "sync_upload_album_setting_subtitle".t(context: context),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
titleStyle: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
subtitleStyle: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
titleStyle: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
subtitleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary),
|
||||
onChanged: handleSyncAlbumToggle,
|
||||
),
|
||||
|
||||
ListTile(
|
||||
title: Text(
|
||||
"albums_on_device_count".t(
|
||||
context: context,
|
||||
args: {'count': albumCount.toString()},
|
||||
),
|
||||
"albums_on_device_count".t(context: context, args: {'count': albumCount.toString()}),
|
||||
style: context.textTheme.titleSmall,
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text(
|
||||
"backup_album_selection_page_albums_tap",
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor),
|
||||
).t(context: context),
|
||||
),
|
||||
trailing: IconButton(
|
||||
splashRadius: 16,
|
||||
icon: Icon(
|
||||
Icons.info,
|
||||
size: 20,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.info, size: 20, color: context.primaryColor),
|
||||
onPressed: () {
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
elevation: 5,
|
||||
title: Text(
|
||||
'backup_album_selection_page_selection_info',
|
||||
@@ -237,9 +213,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
children: [
|
||||
const Text(
|
||||
'backup_album_selection_page_assets_scatter',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
style: TextStyle(fontSize: 14),
|
||||
).t(context: context),
|
||||
],
|
||||
),
|
||||
@@ -252,25 +226,16 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
||||
),
|
||||
|
||||
if (Platform.isAndroid)
|
||||
_SelectAllButton(
|
||||
filteredAlbums: filteredAlbums,
|
||||
selectedBackupAlbums: selectedBackupAlbums,
|
||||
),
|
||||
_SelectAllButton(filteredAlbums: filteredAlbums, selectedBackupAlbums: selectedBackupAlbums),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverLayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.crossAxisExtent > 600) {
|
||||
return _AlbumSelectionGrid(
|
||||
filteredAlbums: filteredAlbums,
|
||||
searchQuery: _searchQuery,
|
||||
);
|
||||
return _AlbumSelectionGrid(filteredAlbums: filteredAlbums, searchQuery: _searchQuery);
|
||||
} else {
|
||||
return _AlbumSelectionList(
|
||||
filteredAlbums: filteredAlbums,
|
||||
searchQuery: _searchQuery,
|
||||
);
|
||||
return _AlbumSelectionList(filteredAlbums: filteredAlbums, searchQuery: _searchQuery);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -285,10 +250,7 @@ class _AlbumSelectionList extends StatelessWidget {
|
||||
final List<LocalAlbum> filteredAlbums;
|
||||
final String searchQuery;
|
||||
|
||||
const _AlbumSelectionList({
|
||||
required this.filteredAlbums,
|
||||
required this.searchQuery,
|
||||
});
|
||||
const _AlbumSelectionList({required this.filteredAlbums, required this.searchQuery});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -304,24 +266,15 @@ class _AlbumSelectionList extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (filteredAlbums.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
((context, index) {
|
||||
return DriftAlbumInfoListTile(
|
||||
album: filteredAlbums[index],
|
||||
);
|
||||
}),
|
||||
childCount: filteredAlbums.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(((context, index) {
|
||||
return DriftAlbumInfoListTile(album: filteredAlbums[index]);
|
||||
}), childCount: filteredAlbums.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -331,10 +284,7 @@ class _AlbumSelectionGrid extends StatelessWidget {
|
||||
final List<LocalAlbum> filteredAlbums;
|
||||
final String searchQuery;
|
||||
|
||||
const _AlbumSelectionGrid({
|
||||
required this.filteredAlbums,
|
||||
required this.searchQuery,
|
||||
});
|
||||
const _AlbumSelectionGrid({required this.filteredAlbums, required this.searchQuery});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -350,11 +300,7 @@ class _AlbumSelectionGrid extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (filteredAlbums.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
return SliverPadding(
|
||||
@@ -367,9 +313,7 @@ class _AlbumSelectionGrid extends StatelessWidget {
|
||||
),
|
||||
itemCount: filteredAlbums.length,
|
||||
itemBuilder: ((context, index) {
|
||||
return DriftAlbumInfoListTile(
|
||||
album: filteredAlbums[index],
|
||||
);
|
||||
return DriftAlbumInfoListTile(album: filteredAlbums[index]);
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -379,9 +323,7 @@ class _AlbumSelectionGrid extends StatelessWidget {
|
||||
class _SelectedAlbumNameChips extends ConsumerWidget {
|
||||
final List<LocalAlbum> selectedBackupAlbums;
|
||||
|
||||
const _SelectedAlbumNameChips({
|
||||
required this.selectedBackupAlbums,
|
||||
});
|
||||
const _SelectedAlbumNameChips({required this.selectedBackupAlbums});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -411,10 +353,7 @@ class _SelectedAlbumNameChips extends ConsumerWidget {
|
||||
),
|
||||
backgroundColor: context.primaryColor,
|
||||
deleteIconColor: context.isDarkTheme ? Colors.black : Colors.white,
|
||||
deleteIcon: const Icon(
|
||||
Icons.cancel_rounded,
|
||||
size: 15,
|
||||
),
|
||||
deleteIcon: const Icon(Icons.cancel_rounded, size: 15),
|
||||
onDeleted: removeSelection,
|
||||
),
|
||||
),
|
||||
@@ -428,9 +367,7 @@ class _SelectedAlbumNameChips extends ConsumerWidget {
|
||||
class _ExcludedAlbumNameChips extends ConsumerWidget {
|
||||
final List<LocalAlbum> excludedBackupAlbums;
|
||||
|
||||
const _ExcludedAlbumNameChips({
|
||||
required this.excludedBackupAlbums,
|
||||
});
|
||||
const _ExcludedAlbumNameChips({required this.excludedBackupAlbums});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -452,18 +389,11 @@ class _ExcludedAlbumNameChips extends ConsumerWidget {
|
||||
child: Chip(
|
||||
label: Text(
|
||||
album.name,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: context.scaffoldBackgroundColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 12, color: context.scaffoldBackgroundColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
backgroundColor: Colors.red[300],
|
||||
deleteIconColor: context.scaffoldBackgroundColor,
|
||||
deleteIcon: const Icon(
|
||||
Icons.cancel_rounded,
|
||||
size: 15,
|
||||
),
|
||||
deleteIcon: const Icon(Icons.cancel_rounded, size: 15),
|
||||
onDeleted: removeSelection,
|
||||
),
|
||||
),
|
||||
@@ -478,10 +408,7 @@ class _SelectAllButton extends ConsumerWidget {
|
||||
final List<LocalAlbum> filteredAlbums;
|
||||
final List<LocalAlbum> selectedBackupAlbums;
|
||||
|
||||
const _SelectAllButton({
|
||||
required this.filteredAlbums,
|
||||
required this.selectedBackupAlbums,
|
||||
});
|
||||
const _SelectAllButton({required this.filteredAlbums, required this.selectedBackupAlbums});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -505,13 +432,9 @@ class _SelectAllButton extends ConsumerWidget {
|
||||
icon: const Icon(Icons.select_all),
|
||||
label: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Text(
|
||||
"select_all".t(context: context),
|
||||
),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
child: Text("select_all".t(context: context)),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 12.0)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
@@ -528,9 +451,7 @@ class _SelectAllButton extends ConsumerWidget {
|
||||
: null,
|
||||
icon: const Icon(Icons.deselect),
|
||||
label: Text('deselect_all'.t(context: context)),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
),
|
||||
style: OutlinedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 12.0)),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -16,9 +16,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final uploadItems = ref.watch(
|
||||
driftBackupProvider.select((state) => state.uploadItems),
|
||||
);
|
||||
final uploadItems = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
@@ -36,26 +34,18 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cloud_off_rounded,
|
||||
size: 80,
|
||||
color: context.colorScheme.onSurface.withValues(alpha: 0.3),
|
||||
),
|
||||
Icon(Icons.cloud_off_rounded, size: 80, color: context.colorScheme.onSurface.withValues(alpha: 0.3)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
"no_uploads_in_progress".t(context: context),
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
style: context.textTheme.titleMedium?.copyWith(color: context.colorScheme.onSurface.withValues(alpha: 0.6)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUploadList(
|
||||
Map<String, DriftUploadStatus> uploadItems,
|
||||
) {
|
||||
Widget _buildUploadList(Map<String, DriftUploadStatus> uploadItems) {
|
||||
return ListView.separated(
|
||||
addAutomaticKeepAlives: true,
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -68,10 +58,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUploadCard(
|
||||
BuildContext context,
|
||||
DriftUploadStatus item,
|
||||
) {
|
||||
Widget _buildUploadCard(BuildContext context, DriftUploadStatus item) {
|
||||
final isCompleted = item.progress >= 1.0;
|
||||
final double progressPercentage = (item.progress * 100).clamp(0, 100);
|
||||
|
||||
@@ -79,19 +66,12 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
elevation: 0,
|
||||
color: item.isFailed != null ? context.colorScheme.errorContainer : context.colorScheme.surfaceContainer,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(16),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: context.colorScheme.outline.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
side: BorderSide(color: context.colorScheme.outline.withValues(alpha: 0.1), width: 1),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _showFileDetailDialog(context, item),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(16),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
@@ -105,9 +85,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
path.basename(item.filename),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
@@ -166,18 +144,11 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
if (isCompleted)
|
||||
Icon(
|
||||
Icons.check_circle_rounded,
|
||||
size: 28,
|
||||
color: context.colorScheme.primary,
|
||||
)
|
||||
Icon(Icons.check_circle_rounded, size: 28, color: context.colorScheme.primary)
|
||||
else
|
||||
Text(
|
||||
percentage.toStringAsFixed(0),
|
||||
style: context.textTheme.labelSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10,
|
||||
),
|
||||
style: context.textTheme.labelSmall?.copyWith(fontWeight: FontWeight.bold, fontSize: 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -192,10 +163,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showFileDetailDialog(
|
||||
BuildContext context,
|
||||
DriftUploadStatus item,
|
||||
) async {
|
||||
Future<void> _showFileDetailDialog(BuildContext context, DriftUploadStatus item) async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => FileDetailDialog(uploadStatus: item),
|
||||
@@ -206,10 +174,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
class FileDetailDialog extends ConsumerWidget {
|
||||
final DriftUploadStatus uploadStatus;
|
||||
|
||||
const FileDetailDialog({
|
||||
super.key,
|
||||
required this.uploadStatus,
|
||||
});
|
||||
const FileDetailDialog({super.key, required this.uploadStatus});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -217,29 +182,17 @@ class FileDetailDialog extends ConsumerWidget {
|
||||
insetPadding: const EdgeInsets.all(20),
|
||||
backgroundColor: context.colorScheme.surfaceContainerLow,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(16),
|
||||
),
|
||||
side: BorderSide(
|
||||
color: context.colorScheme.outline.withValues(alpha: 0.2),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
side: BorderSide(color: context.colorScheme.outline.withValues(alpha: 0.2), width: 1),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: context.primaryColor,
|
||||
size: 24,
|
||||
),
|
||||
Icon(Icons.info_outline, color: context.primaryColor, size: 24),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"details".t(context: context),
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -250,10 +203,7 @@ class FileDetailDialog extends ConsumerWidget {
|
||||
future: _getAssetDetails(ref, uploadStatus.taskId),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const SizedBox(
|
||||
height: 200,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
|
||||
}
|
||||
|
||||
final asset = snapshot.data;
|
||||
@@ -270,18 +220,11 @@ class FileDetailDialog extends ConsumerWidget {
|
||||
width: 128,
|
||||
height: 128,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.colorScheme.outline.withValues(alpha: 0.2),
|
||||
width: 1,
|
||||
),
|
||||
border: Border.all(color: context.colorScheme.outline.withValues(alpha: 0.2), width: 1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: asset != null
|
||||
? Thumbnail(
|
||||
asset: asset,
|
||||
size: const Size(512, 512),
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
? Thumbnail(asset: asset, size: const Size(512, 512), fit: BoxFit.cover)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
@@ -289,44 +232,14 @@ class FileDetailDialog extends ConsumerWidget {
|
||||
const SizedBox(height: 24),
|
||||
if (asset != null) ...[
|
||||
_buildInfoSection(context, [
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"Filename",
|
||||
path.basename(uploadStatus.filename),
|
||||
),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"Local ID",
|
||||
asset.id,
|
||||
),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"File Size",
|
||||
formatHumanReadableBytes(uploadStatus.fileSize, 2),
|
||||
),
|
||||
_buildInfoRow(context, "Filename", path.basename(uploadStatus.filename)),
|
||||
_buildInfoRow(context, "Local ID", asset.id),
|
||||
_buildInfoRow(context, "File Size", formatHumanReadableBytes(uploadStatus.fileSize, 2)),
|
||||
if (asset.width != null) _buildInfoRow(context, "Width", "${asset.width}px"),
|
||||
if (asset.height != null)
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"Height",
|
||||
"${asset.height}px",
|
||||
),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"Created At",
|
||||
asset.createdAt.toString(),
|
||||
),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"Updated At",
|
||||
asset.updatedAt.toString(),
|
||||
),
|
||||
if (asset.checksum != null)
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"Checksum",
|
||||
asset.checksum!,
|
||||
),
|
||||
if (asset.height != null) _buildInfoRow(context, "Height", "${asset.height}px"),
|
||||
_buildInfoRow(context, "Created At", asset.createdAt.toString()),
|
||||
_buildInfoRow(context, "Updated At", asset.updatedAt.toString()),
|
||||
if (asset.checksum != null) _buildInfoRow(context, "Checksum", asset.checksum!),
|
||||
]),
|
||||
],
|
||||
],
|
||||
@@ -340,39 +253,23 @@ class FileDetailDialog extends ConsumerWidget {
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
"close".t(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoSection(
|
||||
BuildContext context,
|
||||
List<Widget> children,
|
||||
) {
|
||||
Widget _buildInfoSection(BuildContext context, List<Widget> children) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
border: Border.all(
|
||||
color: context.colorScheme.outline.withValues(alpha: 0.1),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
...children,
|
||||
],
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
border: Border.all(color: context.colorScheme.outline.withValues(alpha: 0.1), width: 1),
|
||||
),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [...children]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -405,10 +302,7 @@ class FileDetailDialog extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<LocalAsset?> _getAssetDetails(
|
||||
WidgetRef ref,
|
||||
String localAssetId,
|
||||
) async {
|
||||
Future<LocalAsset?> _getAssetDetails(WidgetRef ref, String localAssetId) async {
|
||||
try {
|
||||
final repository = ref.read(localAssetRepository);
|
||||
return await repository.getById(localAssetId);
|
||||
|
||||
@@ -25,9 +25,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
context.maybePop(true);
|
||||
},
|
||||
splashRadius: 24,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
@@ -37,19 +35,13 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
var errorAsset = errorBackupList.elementAt(index);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0,
|
||||
vertical: 4,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4),
|
||||
child: Card(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(15), // if you need this
|
||||
),
|
||||
side: BorderSide(
|
||||
color: Colors.black12,
|
||||
width: 1,
|
||||
),
|
||||
side: BorderSide(color: Colors.black12, width: 1),
|
||||
),
|
||||
elevation: 0,
|
||||
child: Row(
|
||||
@@ -57,12 +49,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 100,
|
||||
minHeight: 100,
|
||||
maxWidth: 100,
|
||||
maxHeight: 150,
|
||||
),
|
||||
constraints: const BoxConstraints(minWidth: 100, minHeight: 100, maxWidth: 100, maxHeight: 150),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(15),
|
||||
@@ -71,11 +58,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Image(
|
||||
fit: BoxFit.cover,
|
||||
image: ImmichLocalThumbnailProvider(
|
||||
asset: errorAsset.asset,
|
||||
height: 512,
|
||||
width: 512,
|
||||
),
|
||||
image: ImmichLocalThumbnailProvider(asset: errorAsset.asset, height: 512, width: 512),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -91,20 +74,14 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMMd().format(
|
||||
DateTime.parse(
|
||||
errorAsset.fileCreatedAt.toString(),
|
||||
).toLocal(),
|
||||
DateTime.parse(errorAsset.fileCreatedAt.toString()).toLocal(),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: context.isDarkTheme ? Colors.white70 : Colors.grey[800],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.error,
|
||||
color: Colors.red.withAlpha(200),
|
||||
size: 18,
|
||||
),
|
||||
Icon(Icons.error, color: Colors.red.withAlpha(200), size: 18),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
@@ -113,10 +90,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
|
||||
errorAsset.fileName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.bold, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
|
||||
@@ -16,9 +16,7 @@ import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ActivitiesPage extends HookConsumerWidget {
|
||||
const ActivitiesPage({
|
||||
super.key,
|
||||
});
|
||||
const ActivitiesPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -60,9 +58,7 @@ class ActivitiesPage extends HookConsumerWidget {
|
||||
itemBuilder: (context, index) {
|
||||
// Additional vertical gap after the last element
|
||||
if (index == data.length) {
|
||||
return const SizedBox(
|
||||
height: 80,
|
||||
);
|
||||
return const SizedBox(height: 80);
|
||||
}
|
||||
|
||||
final activity = data[index];
|
||||
@@ -73,8 +69,9 @@ class ActivitiesPage extends HookConsumerWidget {
|
||||
child: DismissibleActivity(
|
||||
activity.id,
|
||||
ActivityTile(activity),
|
||||
onDismiss:
|
||||
canDelete ? (activityId) async => await activityNotifier.removeActivity(activity.id) : null,
|
||||
onDismiss: canDelete
|
||||
? (activityId) async => await activityNotifier.removeActivity(activity.id)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -12,17 +12,13 @@ import 'package:intl/intl.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AppLogPage extends HookConsumerWidget {
|
||||
const AppLogPage({
|
||||
super.key,
|
||||
});
|
||||
const AppLogPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final immichLogger = LogService.I;
|
||||
final shouldReload = useState(false);
|
||||
final logMessages = useFuture(
|
||||
useMemoized(() => immichLogger.getMessages(), [shouldReload.value]),
|
||||
);
|
||||
final logMessages = useFuture(useMemoized(() => immichLogger.getMessages(), [shouldReload.value]));
|
||||
|
||||
Widget colorStatusIndicator(Color color) {
|
||||
return Column(
|
||||
@@ -31,38 +27,29 @@ class AppLogPage extends HookConsumerWidget {
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLeadingIcon(LogLevel level) => switch (level) {
|
||||
LogLevel.info => colorStatusIndicator(context.primaryColor),
|
||||
LogLevel.severe => colorStatusIndicator(Colors.redAccent),
|
||||
LogLevel.warning => colorStatusIndicator(Colors.orangeAccent),
|
||||
_ => colorStatusIndicator(Colors.grey),
|
||||
};
|
||||
LogLevel.info => colorStatusIndicator(context.primaryColor),
|
||||
LogLevel.severe => colorStatusIndicator(Colors.redAccent),
|
||||
LogLevel.warning => colorStatusIndicator(Colors.orangeAccent),
|
||||
_ => colorStatusIndicator(Colors.grey),
|
||||
};
|
||||
|
||||
Color getTileColor(LogLevel level) => switch (level) {
|
||||
LogLevel.info => Colors.transparent,
|
||||
LogLevel.severe => Colors.redAccent.withValues(alpha: 0.25),
|
||||
LogLevel.warning => Colors.orangeAccent.withValues(alpha: 0.25),
|
||||
_ => context.primaryColor.withValues(alpha: 0.1),
|
||||
};
|
||||
LogLevel.info => Colors.transparent,
|
||||
LogLevel.severe => Colors.redAccent.withValues(alpha: 0.25),
|
||||
LogLevel.warning => Colors.orangeAccent.withValues(alpha: 0.25),
|
||||
_ => context.primaryColor.withValues(alpha: 0.1),
|
||||
};
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
"Logs",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
title: const Text("Logs", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0)),
|
||||
scrolledUnderElevation: 1,
|
||||
elevation: 2,
|
||||
actions: [
|
||||
@@ -81,12 +68,7 @@ class AppLogPage extends HookConsumerWidget {
|
||||
Builder(
|
||||
builder: (BuildContext iconContext) {
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
Icons.share_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: "Share logs",
|
||||
size: 20.0,
|
||||
),
|
||||
icon: Icon(Icons.share_rounded, color: context.primaryColor, semanticLabel: "Share logs", size: 20.0),
|
||||
onPressed: () {
|
||||
ImmichLogger.shareLogs(iconContext);
|
||||
},
|
||||
@@ -98,10 +80,7 @@ class AppLogPage extends HookConsumerWidget {
|
||||
onPressed: () {
|
||||
context.maybePop();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 20.0,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20.0),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
@@ -113,11 +92,7 @@ class AppLogPage extends HookConsumerWidget {
|
||||
itemBuilder: (context, index) {
|
||||
var logMessage = logMessages.data![index];
|
||||
return ListTile(
|
||||
onTap: () => context.pushRoute(
|
||||
AppLogDetailRoute(
|
||||
logMessage: logMessage,
|
||||
),
|
||||
),
|
||||
onTap: () => context.pushRoute(AppLogDetailRoute(logMessage: logMessage)),
|
||||
trailing: const Icon(Icons.arrow_forward_ios_rounded),
|
||||
visualDensity: VisualDensity.compact,
|
||||
dense: true,
|
||||
@@ -125,18 +100,11 @@ class AppLogPage extends HookConsumerWidget {
|
||||
minLeadingWidth: 10,
|
||||
title: Text(
|
||||
truncateLogMessage(logMessage.message, 4),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: context.colorScheme.onSurface,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
style: TextStyle(fontSize: 14.0, color: context.colorScheme.onSurface, fontFamily: "Inconsolata"),
|
||||
),
|
||||
subtitle: Text(
|
||||
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.logger}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
style: TextStyle(fontSize: 12.0, color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
leading: buildLeadingIcon(logMessage.level),
|
||||
);
|
||||
|
||||
@@ -27,11 +27,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
header,
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: context.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 12.0, color: context.primaryColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
@@ -41,38 +37,26 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Copied to clipboard",
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.copy,
|
||||
size: 16.0,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.copy, size: 16.0, color: context.primaryColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15.0)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -91,29 +75,19 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
"FROM",
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: context.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 12.0, color: context.primaryColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15.0)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText(
|
||||
context1.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -123,20 +97,14 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Log Detail"),
|
||||
),
|
||||
appBar: AppBar(title: const Text("Log Detail")),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
buildTextWithCopyButton("MESSAGE", logMessage.message),
|
||||
if (logMessage.error != null) buildTextWithCopyButton("DETAILS", logMessage.error.toString()),
|
||||
if (logMessage.logger != null) buildLogContext1(logMessage.logger.toString()),
|
||||
if (logMessage.stack != null)
|
||||
buildTextWithCopyButton(
|
||||
"STACK TRACE",
|
||||
logMessage.stack.toString(),
|
||||
),
|
||||
if (logMessage.stack != null) buildTextWithCopyButton("STACK TRACE", logMessage.stack.toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -49,11 +49,9 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||
|
||||
// Cancel uploads
|
||||
await Store.put(StoreKey.backgroundBackup, false);
|
||||
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
||||
enabled: false,
|
||||
onBatteryInfo: () {},
|
||||
onError: (_) {},
|
||||
);
|
||||
ref
|
||||
.read(backupProvider.notifier)
|
||||
.configureBackgroundBackup(enabled: false, onBatteryInfo: () {}, onError: (_) {});
|
||||
ref.read(backupProvider.notifier).setAutoBackup(false);
|
||||
ref.read(backupProvider.notifier).cancelBackup();
|
||||
ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||
@@ -65,14 +63,8 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||
|
||||
if (permission.isGranted) {
|
||||
await ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||
await migrateDeviceAssetToSqlite(
|
||||
ref.read(isarProvider),
|
||||
ref.read(driftProvider),
|
||||
);
|
||||
await migrateBackupAlbumsToSqlite(
|
||||
ref.read(isarProvider),
|
||||
ref.read(driftProvider),
|
||||
);
|
||||
await migrateDeviceAssetToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||
await migrateBackupAlbumsToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||
}
|
||||
} else {
|
||||
await ref.read(backgroundSyncProvider).cancel();
|
||||
@@ -98,16 +90,8 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||
AnimatedSwitcher(
|
||||
duration: Durations.long4,
|
||||
child: hasMigrated
|
||||
? const Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.green,
|
||||
size: 48.0,
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 50.0,
|
||||
height: 50.0,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
? const Icon(Icons.check_circle_rounded, color: Colors.green, size: 48.0)
|
||||
: const SizedBox(width: 50.0, height: 50.0, child: CircularProgressIndicator()),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Center(
|
||||
|
||||
@@ -20,10 +20,7 @@ import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
||||
class CreateAlbumPage extends HookConsumerWidget {
|
||||
final List<Asset>? assets;
|
||||
|
||||
const CreateAlbumPage({
|
||||
super.key,
|
||||
this.assets,
|
||||
});
|
||||
const CreateAlbumPage({super.key, this.assets});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -32,9 +29,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
final albumDescriptionTextFieldFocusNode = useFocusNode();
|
||||
final isAlbumTitleTextFieldFocus = useState(false);
|
||||
final isAlbumTitleEmpty = useState(true);
|
||||
final selectedAssets = useState<Set<Asset>>(
|
||||
assets != null ? Set.from(assets!) : const {},
|
||||
);
|
||||
final selectedAssets = useState<Set<Asset>>(assets != null ? Set.from(assets!) : const {});
|
||||
|
||||
void onBackgroundTapped() {
|
||||
albumTitleTextFieldFocusNode.unfocus();
|
||||
@@ -50,10 +45,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
onSelectPhotosButtonPressed() async {
|
||||
AssetSelectionPageResult? selectedAsset = await context.pushRoute<AssetSelectionPageResult?>(
|
||||
AlbumAssetSelectionRoute(
|
||||
existingAssets: selectedAssets.value,
|
||||
canDeselect: true,
|
||||
),
|
||||
AlbumAssetSelectionRoute(existingAssets: selectedAssets.value, canDeselect: true),
|
||||
);
|
||||
if (selectedAsset == null) {
|
||||
selectedAssets.value = const {};
|
||||
@@ -64,10 +56,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
buildTitleInputField() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 10,
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 10, left: 10),
|
||||
child: AlbumTitleTextField(
|
||||
isAlbumTitleEmpty: isAlbumTitleEmpty,
|
||||
albumTitleTextFieldFocusNode: albumTitleTextFieldFocusNode,
|
||||
@@ -79,10 +68,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
buildDescriptionInputField() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 10,
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 10, left: 10),
|
||||
child: AlbumViewerEditableDescription(
|
||||
albumDescription: '',
|
||||
descriptionFocusNode: albumDescriptionTextFieldFocusNode,
|
||||
@@ -95,10 +81,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 200, left: 18),
|
||||
child: Text(
|
||||
'create_shared_album_page_share_add_assets',
|
||||
style: context.textTheme.labelLarge,
|
||||
).tr(),
|
||||
child: Text('create_shared_album_page_share_add_assets', style: context.textTheme.labelLarge).tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -115,18 +98,11 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
style: FilledButton.styleFrom(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10),
|
||||
),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
backgroundColor: context.colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
onPressed: onSelectPhotosButtonPressed,
|
||||
icon: Icon(
|
||||
Icons.add_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.add_rounded, color: context.primaryColor),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
@@ -174,17 +150,12 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
crossAxisSpacing: 5.0,
|
||||
mainAxisSpacing: 5,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
onTap: onBackgroundTapped,
|
||||
child: SharedAlbumThumbnailImage(
|
||||
asset: selectedAssets.value.elementAt(index),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: selectedAssets.value.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
onTap: onBackgroundTapped,
|
||||
child: SharedAlbumThumbnailImage(asset: selectedAssets.value.elementAt(index)),
|
||||
);
|
||||
}, childCount: selectedAssets.value.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -194,10 +165,9 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
Future<void> createAlbum() async {
|
||||
onBackgroundTapped();
|
||||
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
|
||||
ref.read(albumTitleProvider),
|
||||
selectedAssets.value,
|
||||
);
|
||||
var newAlbum = await ref
|
||||
.watch(albumProvider.notifier)
|
||||
.createAlbum(ref.read(albumTitleProvider), selectedAssets.value);
|
||||
|
||||
if (newAlbum != null) {
|
||||
ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||
@@ -220,9 +190,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
},
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
),
|
||||
title: const Text(
|
||||
'create_album',
|
||||
).tr(),
|
||||
title: const Text('create_album').tr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: albumTitleController.text.isNotEmpty ? createAlbum : null,
|
||||
|
||||
@@ -6,22 +6,13 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
||||
|
||||
class DownloadPanel extends ConsumerWidget {
|
||||
const DownloadPanel({
|
||||
super.key,
|
||||
});
|
||||
const DownloadPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final showProgress = ref.watch(
|
||||
downloadStateProvider.select((state) => state.showProgress),
|
||||
);
|
||||
final showProgress = ref.watch(downloadStateProvider.select((state) => state.showProgress));
|
||||
|
||||
final tasks = ref
|
||||
.watch(
|
||||
downloadStateProvider.select((state) => state.taskProgress),
|
||||
)
|
||||
.entries
|
||||
.toList();
|
||||
final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList();
|
||||
|
||||
onCancelDownload(String id) {
|
||||
ref.watch(downloadStateProvider.notifier).cancelDownload(id);
|
||||
@@ -74,47 +65,35 @@ class DownloadTaskTile extends StatelessWidget {
|
||||
final progressPercent = (progress * 100).round();
|
||||
|
||||
String getStatusText() => switch (status) {
|
||||
TaskStatus.running => 'downloading'.tr(),
|
||||
TaskStatus.complete => 'download_complete'.tr(),
|
||||
TaskStatus.failed => 'download_failed'.tr(),
|
||||
TaskStatus.canceled => 'download_canceled'.tr(),
|
||||
TaskStatus.paused => 'download_paused'.tr(),
|
||||
TaskStatus.enqueued => 'download_enqueue'.tr(),
|
||||
TaskStatus.notFound => 'download_notfound'.tr(),
|
||||
TaskStatus.waitingToRetry => 'download_waiting_to_retry'.tr(),
|
||||
};
|
||||
TaskStatus.running => 'downloading'.tr(),
|
||||
TaskStatus.complete => 'download_complete'.tr(),
|
||||
TaskStatus.failed => 'download_failed'.tr(),
|
||||
TaskStatus.canceled => 'download_canceled'.tr(),
|
||||
TaskStatus.paused => 'download_paused'.tr(),
|
||||
TaskStatus.enqueued => 'download_enqueue'.tr(),
|
||||
TaskStatus.notFound => 'download_notfound'.tr(),
|
||||
TaskStatus.waitingToRetry => 'download_waiting_to_retry'.tr(),
|
||||
};
|
||||
|
||||
return SizedBox(
|
||||
key: const ValueKey('download_progress'),
|
||||
width: context.width - 32,
|
||||
child: Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(16),
|
||||
),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||
child: ListTile(
|
||||
minVerticalPadding: 18,
|
||||
leading: const Icon(Icons.video_file_outlined),
|
||||
title: Text(
|
||||
getStatusText(),
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
title: Text(getStatusText(), style: context.textTheme.labelLarge),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.close, color: context.colorScheme.onError),
|
||||
onPressed: onCancelDownload,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: context.colorScheme.error.withAlpha(200),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: context.colorScheme.error.withAlpha(200)),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
fileName,
|
||||
style: context.textTheme.labelMedium,
|
||||
),
|
||||
Text(fileName, style: context.textTheme.labelMedium),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -125,10 +104,7 @@ class DownloadTaskTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$progressPercent%',
|
||||
style: context.textTheme.labelSmall,
|
||||
),
|
||||
Text('$progressPercent%', style: context.textTheme.labelSmall),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -36,11 +36,7 @@ class GalleryStackedChildren extends HookConsumerWidget {
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stackElements.length,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 30),
|
||||
itemBuilder: (context, index) {
|
||||
final currentAsset = stackElements.elementAt(index);
|
||||
final assetId = currentAsset.remoteId;
|
||||
@@ -63,9 +59,7 @@ class GalleryStackedChildren extends HookConsumerWidget {
|
||||
? 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,11 +87,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
if (index < totalAssets.value && index >= 0) {
|
||||
final asset = loadAsset(index);
|
||||
await precacheImage(
|
||||
ImmichImage.imageProvider(
|
||||
asset: asset,
|
||||
width: context.width,
|
||||
height: context.height,
|
||||
),
|
||||
ImmichImage.imageProvider(asset: asset, width: context.width, height: context.height),
|
||||
context,
|
||||
onError: onError,
|
||||
);
|
||||
@@ -103,23 +99,20 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (ref.read(showControlsProvider)) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
}
|
||||
useEffect(() {
|
||||
if (ref.read(showControlsProvider)) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
}
|
||||
|
||||
// Delay this a bit so we can finish loading the page
|
||||
Timer(const Duration(milliseconds: 400), () {
|
||||
precacheNextImage(currentIndex.value + 1);
|
||||
});
|
||||
// Delay this a bit so we can finish loading the page
|
||||
Timer(const Duration(milliseconds: 400), () {
|
||||
precacheNextImage(currentIndex.value + 1);
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
const [],
|
||||
);
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
useEffect(() {
|
||||
final asset = loadAsset(currentIndex.value);
|
||||
@@ -136,9 +129,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
duration: const Duration(seconds: 1),
|
||||
content: Text(
|
||||
"local_asset_cast_failed".tr(),
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -147,9 +138,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [
|
||||
ref.watch(castProvider).isCasting,
|
||||
]);
|
||||
}, [ref.watch(castProvider).isCasting]);
|
||||
|
||||
void showInfo() {
|
||||
final asset = ref.read(currentAssetProvider);
|
||||
@@ -157,9 +146,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
showModalBottomSheet(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
||||
barrierColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
showDragHandle: true,
|
||||
@@ -174,20 +161,10 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
expand: false,
|
||||
builder: (context, scrollController) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: context.viewInsets.bottom,
|
||||
),
|
||||
child: ref.watch(appSettingsServiceProvider).getSetting<bool>(
|
||||
AppSettingsEnum.advancedTroubleshooting,
|
||||
)
|
||||
? AdvancedBottomSheet(
|
||||
assetDetail: asset,
|
||||
scrollController: scrollController,
|
||||
)
|
||||
: DetailPanel(
|
||||
asset: asset,
|
||||
scrollController: scrollController,
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: context.viewInsets.bottom),
|
||||
child: ref.watch(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.advancedTroubleshooting)
|
||||
? AdvancedBottomSheet(assetDetail: asset, scrollController: scrollController)
|
||||
: DetailPanel(asset: asset, scrollController: scrollController),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -258,10 +235,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
tightMode: true,
|
||||
initialScale: PhotoViewComputedScale.contained * 0.99,
|
||||
minScale: PhotoViewComputedScale.contained * 0.99,
|
||||
errorBuilder: (context, error, stackTrace) => ImmichImage(
|
||||
asset,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
errorBuilder: (context, error, stackTrace) => ImmichImage(asset, fit: BoxFit.contain),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -283,11 +257,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
asset: asset,
|
||||
image: Image(
|
||||
key: ValueKey(asset),
|
||||
image: ImmichImage.imageProvider(
|
||||
asset: asset,
|
||||
width: context.width,
|
||||
height: context.height,
|
||||
),
|
||||
image: ImmichImage.imageProvider(asset: asset, width: context.width, height: context.height),
|
||||
fit: BoxFit.contain,
|
||||
height: context.height,
|
||||
width: context.width,
|
||||
@@ -342,17 +312,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
BackdropFilter(
|
||||
filter: ui.ImageFilter.blur(
|
||||
sigmaX: 10,
|
||||
sigmaY: 10,
|
||||
),
|
||||
),
|
||||
ImmichThumbnail(
|
||||
key: ValueKey(asset),
|
||||
asset: asset,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
BackdropFilter(filter: ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10)),
|
||||
ImmichThumbnail(key: ValueKey(asset), asset: asset, fit: BoxFit.contain),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -361,9 +322,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
scrollPhysics: isZoomed.value
|
||||
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
|
||||
: (Platform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics() // Use heavy physics for Android
|
||||
),
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics() // Use heavy physics for Android
|
||||
),
|
||||
itemCount: totalAssets.value,
|
||||
scrollDirection: Axis.horizontal,
|
||||
onPageChanged: (value, _) {
|
||||
@@ -401,9 +362,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
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),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -416,10 +375,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: GalleryAppBar(
|
||||
key: const ValueKey('app-bar'),
|
||||
showInfo: showInfo,
|
||||
),
|
||||
child: GalleryAppBar(key: const ValueKey('app-bar'), showInfo: showInfo),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
|
||||
@@ -79,10 +79,8 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (ctx, index) => list[index],
|
||||
separatorBuilder: (context, index) => const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.0, left: 8, right: 8),
|
||||
child: Divider(),
|
||||
),
|
||||
separatorBuilder: (context, index) =>
|
||||
const Padding(padding: EdgeInsets.only(bottom: 16.0, left: 8, right: 8), child: Divider()),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -109,12 +107,9 @@ class HeaderKeyValueSettings extends StatelessWidget {
|
||||
final SettingsHeader header;
|
||||
final Function() onRemove;
|
||||
|
||||
HeaderKeyValueSettings({
|
||||
super.key,
|
||||
required this.header,
|
||||
required this.onRemove,
|
||||
}) : keyController = TextEditingController(text: header.key),
|
||||
valueController = TextEditingController(text: header.value);
|
||||
HeaderKeyValueSettings({super.key, required this.header, required this.onRemove})
|
||||
: keyController = TextEditingController(text: header.key),
|
||||
valueController = TextEditingController(text: header.value);
|
||||
|
||||
String? emptyFieldValidator(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
@@ -150,9 +145,7 @@ class HeaderKeyValueSettings extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: IconButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 12)),
|
||||
color: Colors.red[400],
|
||||
onPressed: onRemove,
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
|
||||
@@ -8,10 +8,7 @@ class LargeLeadingTile extends StatelessWidget {
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.leadingPadding = const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
this.leadingPadding = const EdgeInsets.symmetric(vertical: 8, horizontal: 16.0),
|
||||
this.borderRadius = 20.0,
|
||||
this.trailing,
|
||||
this.selected = false,
|
||||
@@ -47,18 +44,12 @@ class LargeLeadingTile extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: leadingPadding,
|
||||
child: leading,
|
||||
),
|
||||
Padding(padding: leadingPadding, child: leading),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.width * 0.6,
|
||||
child: title,
|
||||
),
|
||||
SizedBox(width: context.width * 0.6, child: title),
|
||||
subtitle ?? const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -78,17 +78,15 @@ class NativeVideoViewerPage 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;
|
||||
}
|
||||
|
||||
// Use a network URL for the video player controller
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final isOriginalVideo =
|
||||
ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loadOriginalVideo);
|
||||
final isOriginalVideo = ref
|
||||
.read(appSettingsServiceProvider)
|
||||
.getSetting<bool>(AppSettingsEnum.loadOriginalVideo);
|
||||
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
||||
final String videoUrl = asset.livePhotoVideoId != null
|
||||
? '$serverEndpoint/assets/${asset.livePhotoVideoId}/$postfixUrl'
|
||||
@@ -101,30 +99,24 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
return source;
|
||||
} catch (error) {
|
||||
log.severe(
|
||||
'Error creating video source for asset ${asset.fileName}: $error',
|
||||
);
|
||||
log.severe('Error creating video source for asset ${asset.fileName}: $error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final videoSource = useMemoized<Future<VideoSource?>>(() => createSource());
|
||||
final aspectRatio = useState<double?>(asset.aspectRatio);
|
||||
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.fileName}: $error',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
try {
|
||||
aspectRatio.value = await ref.read(assetServiceProvider).getAspectRatio(asset);
|
||||
} catch (error) {
|
||||
log.severe('Error getting aspect ratio for asset ${asset.fileName}: $error');
|
||||
}
|
||||
});
|
||||
|
||||
void checkIfBuffering() {
|
||||
if (!context.mounted) {
|
||||
@@ -134,8 +126,9 @@ class NativeVideoViewerPage 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,48 +315,42 @@ class NativeVideoViewerPage 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) {
|
||||
@@ -393,12 +380,7 @@ class NativeVideoViewerPage 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -18,67 +18,31 @@ import 'package:immich_mobile/widgets/settings/preference_settings/preference_se
|
||||
import 'package:immich_mobile/widgets/settings/settings_card.dart';
|
||||
|
||||
enum SettingSection {
|
||||
beta(
|
||||
'beta_sync',
|
||||
Icons.sync_outlined,
|
||||
"beta_sync_subtitle",
|
||||
),
|
||||
advanced(
|
||||
'advanced',
|
||||
Icons.build_outlined,
|
||||
"advanced_settings_tile_subtitle",
|
||||
),
|
||||
assetViewer(
|
||||
'asset_viewer_settings_title',
|
||||
Icons.image_outlined,
|
||||
"asset_viewer_settings_subtitle",
|
||||
),
|
||||
backup(
|
||||
'backup',
|
||||
Icons.cloud_upload_outlined,
|
||||
"backup_setting_subtitle",
|
||||
),
|
||||
languages(
|
||||
'language',
|
||||
Icons.language,
|
||||
"setting_languages_subtitle",
|
||||
),
|
||||
networking(
|
||||
'networking_settings',
|
||||
Icons.wifi,
|
||||
"networking_subtitle",
|
||||
),
|
||||
notifications(
|
||||
'notifications',
|
||||
Icons.notifications_none_rounded,
|
||||
"setting_notifications_subtitle",
|
||||
),
|
||||
preferences(
|
||||
'preferences_settings_title',
|
||||
Icons.interests_outlined,
|
||||
"preferences_settings_subtitle",
|
||||
),
|
||||
timeline(
|
||||
'asset_list_settings_title',
|
||||
Icons.auto_awesome_mosaic_outlined,
|
||||
"asset_list_settings_subtitle",
|
||||
);
|
||||
beta('beta_sync', Icons.sync_outlined, "beta_sync_subtitle"),
|
||||
advanced('advanced', Icons.build_outlined, "advanced_settings_tile_subtitle"),
|
||||
assetViewer('asset_viewer_settings_title', Icons.image_outlined, "asset_viewer_settings_subtitle"),
|
||||
backup('backup', Icons.cloud_upload_outlined, "backup_setting_subtitle"),
|
||||
languages('language', Icons.language, "setting_languages_subtitle"),
|
||||
networking('networking_settings', Icons.wifi, "networking_subtitle"),
|
||||
notifications('notifications', Icons.notifications_none_rounded, "setting_notifications_subtitle"),
|
||||
preferences('preferences_settings_title', Icons.interests_outlined, "preferences_settings_subtitle"),
|
||||
timeline('asset_list_settings_title', Icons.auto_awesome_mosaic_outlined, "asset_list_settings_subtitle");
|
||||
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final IconData icon;
|
||||
|
||||
Widget get widget => switch (this) {
|
||||
SettingSection.beta => const _BetaLandscapeToggle(),
|
||||
SettingSection.advanced => const AdvancedSettings(),
|
||||
SettingSection.assetViewer => const AssetViewerSettings(),
|
||||
SettingSection.backup => const BackupSettings(),
|
||||
SettingSection.languages => const LanguageSettings(),
|
||||
SettingSection.networking => const NetworkingSettings(),
|
||||
SettingSection.notifications => const NotificationSetting(),
|
||||
SettingSection.preferences => const PreferenceSetting(),
|
||||
SettingSection.timeline => const AssetListSettings(),
|
||||
};
|
||||
SettingSection.beta => const _BetaLandscapeToggle(),
|
||||
SettingSection.advanced => const AdvancedSettings(),
|
||||
SettingSection.assetViewer => const AssetViewerSettings(),
|
||||
SettingSection.backup => const BackupSettings(),
|
||||
SettingSection.languages => const LanguageSettings(),
|
||||
SettingSection.networking => const NetworkingSettings(),
|
||||
SettingSection.notifications => const NotificationSetting(),
|
||||
SettingSection.preferences => const PreferenceSetting(),
|
||||
SettingSection.timeline => const AssetListSettings(),
|
||||
};
|
||||
|
||||
const SettingSection(this.title, this.icon, this.subtitle);
|
||||
}
|
||||
@@ -91,10 +55,7 @@ class SettingsPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
context.locale;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: const Text('settings').tr(),
|
||||
),
|
||||
appBar: AppBar(centerTitle: false, title: const Text('settings').tr()),
|
||||
body: context.isMobile ? const _MobileLayout() : const _TabletLayout(),
|
||||
);
|
||||
}
|
||||
@@ -164,10 +125,7 @@ class _TabletLayout extends HookWidget {
|
||||
),
|
||||
),
|
||||
const VerticalDivider(width: 1),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: selectedSection.value.widget,
|
||||
),
|
||||
Expanded(flex: 4, child: selectedSection.value.widget),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -198,10 +156,7 @@ class SettingsSubPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
context.locale;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: Text(section.title).tr(),
|
||||
),
|
||||
appBar: AppBar(centerTitle: false, title: Text(section.title).tr()),
|
||||
body: section.widget,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,31 +43,26 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||
|
||||
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
||||
(a) => {
|
||||
log.info('Successfully updated auth info with access token: $accessToken'),
|
||||
},
|
||||
ref
|
||||
.read(authProvider.notifier)
|
||||
.saveAuthInfo(accessToken: accessToken)
|
||||
.then(
|
||||
(a) => {log.info('Successfully updated auth info with access token: $accessToken')},
|
||||
onError: (exception) => {
|
||||
log.severe(
|
||||
'Failed to update auth info with access token: $accessToken',
|
||||
),
|
||||
log.severe('Failed to update auth info with access token: $accessToken'),
|
||||
ref.read(authProvider.notifier).logout(),
|
||||
context.replaceRoute(const LoginRoute()),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
log.severe(
|
||||
'Missing crucial offline login info - Logging out completely',
|
||||
);
|
||||
log.severe('Missing crucial offline login info - Logging out completely');
|
||||
ref.read(authProvider.notifier).logout();
|
||||
context.replaceRoute(const LoginRoute());
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.router.current.name == SplashScreenRoute.name) {
|
||||
context.replaceRoute(
|
||||
Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute(),
|
||||
);
|
||||
context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute());
|
||||
}
|
||||
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
@@ -85,11 +80,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Image(
|
||||
image: AssetImage('assets/immich-logo.png'),
|
||||
width: 80,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
child: Image(image: AssetImage('assets/immich-logo.png'), width: 80, filterQuality: FilterQuality.high),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,9 +36,7 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
context.primaryColor,
|
||||
),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(context.primaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -65,51 +63,31 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
final navigationDestinations = [
|
||||
NavigationDestination(
|
||||
label: 'photos'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_library_outlined,
|
||||
),
|
||||
icon: const Icon(Icons.photo_library_outlined),
|
||||
selectedIcon: buildIcon(
|
||||
isProcessing: isRefreshingAssets,
|
||||
icon: Icon(
|
||||
Icons.photo_library,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.photo_library, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'search'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.search_rounded,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.search,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
selectedIcon: Icon(Icons.search, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'albums'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_album_outlined,
|
||||
),
|
||||
icon: const Icon(Icons.photo_album_outlined),
|
||||
selectedIcon: buildIcon(
|
||||
isProcessing: isRefreshingRemoteAlbums,
|
||||
icon: Icon(
|
||||
Icons.photo_album_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.photo_album_rounded, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'library'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.space_dashboard_outlined,
|
||||
),
|
||||
icon: const Icon(Icons.space_dashboard_outlined),
|
||||
selectedIcon: buildIcon(
|
||||
isProcessing: isRefreshingAssets,
|
||||
icon: Icon(
|
||||
Icons.space_dashboard_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.space_dashboard_rounded, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
];
|
||||
@@ -125,13 +103,7 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
Widget navigationRail(TabsRouter tabsRouter) {
|
||||
return NavigationRail(
|
||||
destinations: navigationDestinations
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(e.label),
|
||||
selectedIcon: e.selectedIcon,
|
||||
),
|
||||
)
|
||||
.map((e) => NavigationRailDestination(icon: e.icon, label: Text(e.label), selectedIcon: e.selectedIcon))
|
||||
.toList(),
|
||||
onDestinationSelected: (index) => onNavigationSelected(tabsRouter, index),
|
||||
selectedIndex: tabsRouter.activeIndex,
|
||||
@@ -142,17 +114,9 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
|
||||
final multiselectEnabled = ref.watch(multiselectProvider);
|
||||
return AutoTabsRouter(
|
||||
routes: [
|
||||
const PhotosRoute(),
|
||||
SearchRoute(),
|
||||
const AlbumsRoute(),
|
||||
const LibraryRoute(),
|
||||
],
|
||||
routes: [const PhotosRoute(), SearchRoute(), const AlbumsRoute(), const LibraryRoute()],
|
||||
duration: const Duration(milliseconds: 600),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(opacity: animation, child: child),
|
||||
builder: (context, child) {
|
||||
final tabsRouter = AutoTabsRouter.of(context);
|
||||
return PopScope(
|
||||
|
||||
@@ -56,56 +56,30 @@ class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||
final navigationDestinations = [
|
||||
NavigationDestination(
|
||||
label: 'photos'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_library_outlined,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.photo_library,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.photo_library_outlined),
|
||||
selectedIcon: Icon(Icons.photo_library, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'search'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.search_rounded,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.search,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
selectedIcon: Icon(Icons.search, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'albums'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_album_outlined,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.photo_album_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.photo_album_outlined),
|
||||
selectedIcon: Icon(Icons.photo_album_rounded, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'library'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.space_dashboard_outlined,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.space_dashboard_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.space_dashboard_outlined),
|
||||
selectedIcon: Icon(Icons.space_dashboard_rounded, color: context.primaryColor),
|
||||
),
|
||||
];
|
||||
|
||||
Widget navigationRail(TabsRouter tabsRouter) {
|
||||
return NavigationRail(
|
||||
destinations: navigationDestinations
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(e.label),
|
||||
selectedIcon: e.selectedIcon,
|
||||
),
|
||||
)
|
||||
.map((e) => NavigationRailDestination(icon: e.icon, label: Text(e.label), selectedIcon: e.selectedIcon))
|
||||
.toList(),
|
||||
onDestinationSelected: (index) => _onNavigationSelected(tabsRouter, index, ref),
|
||||
selectedIndex: tabsRouter.activeIndex,
|
||||
@@ -115,17 +89,9 @@ class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||
}
|
||||
|
||||
return AutoTabsRouter(
|
||||
routes: [
|
||||
const MainTimelineRoute(),
|
||||
DriftSearchRoute(),
|
||||
const DriftAlbumsRoute(),
|
||||
const DriftLibraryRoute(),
|
||||
],
|
||||
routes: [const MainTimelineRoute(), DriftSearchRoute(), const DriftAlbumsRoute(), const DriftLibraryRoute()],
|
||||
duration: const Duration(milliseconds: 600),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(opacity: animation, child: child),
|
||||
builder: (context, child) {
|
||||
final tabsRouter = AutoTabsRouter.of(context);
|
||||
return PopScope(
|
||||
@@ -142,10 +108,7 @@ class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||
],
|
||||
)
|
||||
: child,
|
||||
bottomNavigationBar: _BottomNavigationBar(
|
||||
tabsRouter: tabsRouter,
|
||||
destinations: navigationDestinations,
|
||||
),
|
||||
bottomNavigationBar: _BottomNavigationBar(tabsRouter: tabsRouter, destinations: navigationDestinations),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -175,10 +138,7 @@ void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) {
|
||||
}
|
||||
|
||||
class _BottomNavigationBar extends ConsumerWidget {
|
||||
const _BottomNavigationBar({
|
||||
required this.tabsRouter,
|
||||
required this.destinations,
|
||||
});
|
||||
const _BottomNavigationBar({required this.tabsRouter, required this.destinations});
|
||||
|
||||
final List<Widget> destinations;
|
||||
final TabsRouter tabsRouter;
|
||||
|
||||
@@ -32,20 +32,10 @@ class CropImagePage extends HookWidget {
|
||||
leading: CloseButton(color: context.primaryColor),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.done_rounded,
|
||||
color: context.primaryColor,
|
||||
size: 24,
|
||||
),
|
||||
icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () async {
|
||||
final croppedImage = await cropController.croppedImage();
|
||||
context.pushRoute(
|
||||
EditImageRoute(
|
||||
asset: asset,
|
||||
image: croppedImage,
|
||||
isEdited: true,
|
||||
),
|
||||
);
|
||||
context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true));
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -60,11 +50,7 @@ class CropImagePage extends HookWidget {
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
width: constraints.maxWidth * 0.9,
|
||||
height: constraints.maxHeight * 0.6,
|
||||
child: CropImage(
|
||||
controller: cropController,
|
||||
image: image,
|
||||
gridColor: Colors.white,
|
||||
),
|
||||
child: CropImage(controller: cropController, image: image, gridColor: Colors.white),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
@@ -81,28 +67,18 @@ class CropImagePage extends HookWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 10,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.rotate_left,
|
||||
color: context.themeData.iconTheme.color,
|
||||
),
|
||||
icon: Icon(Icons.rotate_left, color: context.themeData.iconTheme.color),
|
||||
onPressed: () {
|
||||
cropController.rotateLeft();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.rotate_right,
|
||||
color: context.themeData.iconTheme.color,
|
||||
),
|
||||
icon: Icon(Icons.rotate_right, color: context.themeData.iconTheme.color),
|
||||
onPressed: () {
|
||||
cropController.rotateRight();
|
||||
},
|
||||
@@ -178,17 +154,14 @@ class _AspectRatioButton extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
switch (label) {
|
||||
'Free' => Icons.crop_free_rounded,
|
||||
'1:1' => Icons.crop_square_rounded,
|
||||
'16:9' => Icons.crop_16_9_rounded,
|
||||
'3:2' => Icons.crop_3_2_rounded,
|
||||
'7:5' => Icons.crop_7_5_rounded,
|
||||
_ => Icons.crop_free_rounded,
|
||||
},
|
||||
color: aspectRatio.value == ratio ? context.primaryColor : context.themeData.iconTheme.color,
|
||||
),
|
||||
icon: Icon(switch (label) {
|
||||
'Free' => Icons.crop_free_rounded,
|
||||
'1:1' => Icons.crop_square_rounded,
|
||||
'16:9' => Icons.crop_16_9_rounded,
|
||||
'3:2' => Icons.crop_3_2_rounded,
|
||||
'7:5' => Icons.crop_7_5_rounded,
|
||||
_ => Icons.crop_free_rounded,
|
||||
}, color: aspectRatio.value == ratio ? context.primaryColor : context.themeData.iconTheme.color),
|
||||
onPressed: () {
|
||||
cropController.crop = const Rect.fromLTRB(0.1, 0.1, 0.9, 0.9);
|
||||
aspectRatio.value = ratio;
|
||||
|
||||
@@ -29,51 +29,34 @@ class EditImagePage extends ConsumerWidget {
|
||||
final Image image;
|
||||
final bool isEdited;
|
||||
|
||||
const EditImagePage({
|
||||
super.key,
|
||||
required this.asset,
|
||||
required this.image,
|
||||
required this.isEdited,
|
||||
});
|
||||
const EditImagePage({super.key, required this.asset, required this.image, required this.isEdited});
|
||||
Future<Uint8List> _imageToUint8List(Image image) async {
|
||||
final Completer<Uint8List> completer = Completer();
|
||||
image.image.resolve(const ImageConfiguration()).addListener(
|
||||
ImageStreamListener(
|
||||
(ImageInfo info, bool _) {
|
||||
info.image.toByteData(format: ImageByteFormat.png).then((byteData) {
|
||||
if (byteData != null) {
|
||||
completer.complete(byteData.buffer.asUint8List());
|
||||
} else {
|
||||
completer.completeError('Failed to convert image to bytes');
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: (exception, stackTrace) => completer.completeError(exception),
|
||||
),
|
||||
image.image
|
||||
.resolve(const ImageConfiguration())
|
||||
.addListener(
|
||||
ImageStreamListener((ImageInfo info, bool _) {
|
||||
info.image.toByteData(format: ImageByteFormat.png).then((byteData) {
|
||||
if (byteData != null) {
|
||||
completer.complete(byteData.buffer.asUint8List());
|
||||
} else {
|
||||
completer.completeError('Failed to convert image to bytes');
|
||||
}
|
||||
});
|
||||
}, onError: (exception, stackTrace) => completer.completeError(exception)),
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<void> _saveEditedImage(
|
||||
BuildContext context,
|
||||
Asset asset,
|
||||
Image image,
|
||||
WidgetRef ref,
|
||||
) async {
|
||||
Future<void> _saveEditedImage(BuildContext context, Asset asset, Image image, WidgetRef ref) async {
|
||||
try {
|
||||
final Uint8List imageData = await _imageToUint8List(image);
|
||||
await ref.read(fileMediaRepositoryProvider).saveImage(
|
||||
imageData,
|
||||
title: "${p.withoutExtension(asset.fileName)}_edited.jpg",
|
||||
);
|
||||
await ref
|
||||
.read(fileMediaRepositoryProvider)
|
||||
.saveImage(imageData, title: "${p.withoutExtension(asset.fileName)}_edited.jpg");
|
||||
await ref.read(albumProvider.notifier).refreshDeviceAlbums();
|
||||
context.navigator.popUntil((route) => route.isFirst);
|
||||
ImmichToast.show(
|
||||
durationInSecond: 3,
|
||||
context: context,
|
||||
msg: 'Image Saved!',
|
||||
gravity: ToastGravity.CENTER,
|
||||
);
|
||||
ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!', gravity: ToastGravity.CENTER);
|
||||
} catch (e) {
|
||||
ImmichToast.show(
|
||||
durationInSecond: 6,
|
||||
@@ -91,37 +74,23 @@ class EditImagePage extends ConsumerWidget {
|
||||
title: Text("edit".tr()),
|
||||
backgroundColor: context.scaffoldBackgroundColor,
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Icons.close_rounded,
|
||||
color: context.primaryColor,
|
||||
size: 24,
|
||||
),
|
||||
icon: Icon(Icons.close_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () => context.navigator.popUntil((route) => route.isFirst),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: isEdited ? () => _saveEditedImage(context, asset, image, ref) : null,
|
||||
child: Text(
|
||||
"save_to_gallery".tr(),
|
||||
style: TextStyle(
|
||||
color: isEdited ? context.primaryColor : Colors.grey,
|
||||
),
|
||||
),
|
||||
child: Text("save_to_gallery".tr(), style: TextStyle(color: isEdited ? context.primaryColor : Colors.grey)),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: context.scaffoldBackgroundColor,
|
||||
body: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: context.height * 0.7,
|
||||
maxWidth: context.width * 0.9,
|
||||
),
|
||||
constraints: BoxConstraints(maxHeight: context.height * 0.7, maxWidth: context.width * 0.9),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(7),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(7)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.2),
|
||||
@@ -132,13 +101,8 @@ class EditImagePage extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(7),
|
||||
),
|
||||
child: Image(
|
||||
image: image.image,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(7)),
|
||||
child: Image(image: image.image, fit: BoxFit.contain),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -148,9 +112,7 @@ class EditImagePage extends ConsumerWidget {
|
||||
margin: const EdgeInsets.only(bottom: 60, right: 10, left: 10, top: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: context.scaffoldBackgroundColor,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(30),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(30)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
@@ -159,15 +121,9 @@ class EditImagePage extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.crop_rotate_rounded,
|
||||
color: context.themeData.iconTheme.color,
|
||||
size: 25,
|
||||
),
|
||||
icon: Icon(Icons.crop_rotate_rounded, color: context.themeData.iconTheme.color, size: 25),
|
||||
onPressed: () {
|
||||
context.pushRoute(
|
||||
CropImageRoute(asset: asset, image: image),
|
||||
);
|
||||
context.pushRoute(CropImageRoute(asset: asset, image: image));
|
||||
},
|
||||
),
|
||||
Text("crop".tr(), style: context.textTheme.displayMedium),
|
||||
@@ -177,18 +133,9 @@ class EditImagePage extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.filter,
|
||||
color: context.themeData.iconTheme.color,
|
||||
size: 25,
|
||||
),
|
||||
icon: Icon(Icons.filter, color: context.themeData.iconTheme.color, size: 25),
|
||||
onPressed: () {
|
||||
context.pushRoute(
|
||||
FilterImageRoute(
|
||||
asset: asset,
|
||||
image: image,
|
||||
),
|
||||
);
|
||||
context.pushRoute(FilterImageRoute(asset: asset, image: image));
|
||||
},
|
||||
),
|
||||
Text("filter".tr(), style: context.textTheme.displayMedium),
|
||||
|
||||
@@ -18,21 +18,14 @@ class FilterImagePage extends HookWidget {
|
||||
final Image image;
|
||||
final Asset asset;
|
||||
|
||||
const FilterImagePage({
|
||||
super.key,
|
||||
required this.image,
|
||||
required this.asset,
|
||||
});
|
||||
const FilterImagePage({super.key, required this.image, required this.asset});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorFilter = useState<ColorFilter>(filters[0]);
|
||||
final selectedFilterIndex = useState<int>(0);
|
||||
|
||||
Future<ui.Image> createFilteredImage(
|
||||
ui.Image inputImage,
|
||||
ColorFilter filter,
|
||||
) {
|
||||
Future<ui.Image> createFilteredImage(ui.Image inputImage, ColorFilter filter) {
|
||||
final completer = Completer<ui.Image>();
|
||||
final size = Size(inputImage.width.toDouble(), inputImage.height.toDouble());
|
||||
final recorder = ui.PictureRecorder();
|
||||
@@ -55,11 +48,13 @@ class FilterImagePage extends HookWidget {
|
||||
|
||||
Future<Image> applyFilterAndConvert(ColorFilter filter) async {
|
||||
final completer = Completer<ui.Image>();
|
||||
image.image.resolve(ImageConfiguration.empty).addListener(
|
||||
ImageStreamListener((ImageInfo info, bool _) {
|
||||
completer.complete(info.image);
|
||||
}),
|
||||
);
|
||||
image.image
|
||||
.resolve(ImageConfiguration.empty)
|
||||
.addListener(
|
||||
ImageStreamListener((ImageInfo info, bool _) {
|
||||
completer.complete(info.image);
|
||||
}),
|
||||
);
|
||||
final uiImage = await completer.future;
|
||||
|
||||
final filteredUiImage = await createFilteredImage(uiImage, filter);
|
||||
@@ -76,20 +71,10 @@ class FilterImagePage extends HookWidget {
|
||||
leading: CloseButton(color: context.primaryColor),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.done_rounded,
|
||||
color: context.primaryColor,
|
||||
size: 24,
|
||||
),
|
||||
icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () async {
|
||||
final filteredImage = await applyFilterAndConvert(colorFilter.value);
|
||||
context.pushRoute(
|
||||
EditImageRoute(
|
||||
asset: asset,
|
||||
image: filteredImage,
|
||||
isEdited: true,
|
||||
),
|
||||
);
|
||||
context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true));
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -100,10 +85,7 @@ class FilterImagePage extends HookWidget {
|
||||
SizedBox(
|
||||
height: context.height * 0.7,
|
||||
child: Center(
|
||||
child: ColorFiltered(
|
||||
colorFilter: colorFilter.value,
|
||||
child: image,
|
||||
),
|
||||
child: ColorFiltered(colorFilter: colorFilter.value, child: image),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
@@ -156,21 +138,14 @@ class _FilterButton extends StatelessWidget {
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(10),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
border: isSelected ? Border.all(color: context.primaryColor, width: 3) : null,
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(10),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
child: ColorFiltered(
|
||||
colorFilter: filter,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.cover,
|
||||
child: image,
|
||||
),
|
||||
child: FittedBox(fit: BoxFit.cover, child: image),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -16,15 +16,10 @@ class ArchivePage extends HookConsumerWidget {
|
||||
final archiveRenderList = ref.watch(archiveTimelineProvider);
|
||||
final count = archiveRenderList.value?.totalAssets.toString() ?? "?";
|
||||
return AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
title: const Text(
|
||||
'archive_page_title',
|
||||
).tr(namedArgs: {'count': count}),
|
||||
title: const Text('archive_page_title').tr(namedArgs: {'count': count}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,15 +14,10 @@ class FavoritesPage extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
AppBar buildAppBar() {
|
||||
return AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
title: const Text(
|
||||
'favorites',
|
||||
).tr(),
|
||||
title: const Text('favorites').tr(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,7 @@ import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
RecursiveFolder? _findFolderInStructure(
|
||||
RootFolder rootFolder,
|
||||
RecursiveFolder targetFolder,
|
||||
) {
|
||||
RecursiveFolder? _findFolderInStructure(RootFolder rootFolder, RecursiveFolder targetFolder) {
|
||||
for (final folder in rootFolder.subfolders) {
|
||||
if (targetFolder.path == '/' && folder.path.isEmpty && folder.name == targetFolder.name) {
|
||||
return folder;
|
||||
@@ -49,29 +46,23 @@ class FolderPage extends HookConsumerWidget {
|
||||
final currentFolder = useState<RecursiveFolder?>(folder);
|
||||
final sortOrder = useState<SortOrder>(SortOrder.asc);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (folder == null) {
|
||||
ref.read(folderStructureProvider.notifier).fetchFolders(sortOrder.value);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
if (folder == null) {
|
||||
ref.read(folderStructureProvider.notifier).fetchFolders(sortOrder.value);
|
||||
}
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
// Update current folder when root structure changes
|
||||
useEffect(
|
||||
() {
|
||||
if (folder != null && folderState.hasValue) {
|
||||
final updatedFolder = _findFolderInStructure(folderState.value!, folder!);
|
||||
if (updatedFolder != null) {
|
||||
currentFolder.value = updatedFolder;
|
||||
}
|
||||
useEffect(() {
|
||||
if (folder != null && folderState.hasValue) {
|
||||
final updatedFolder = _findFolderInStructure(folderState.value!, folder!);
|
||||
if (updatedFolder != null) {
|
||||
currentFolder.value = updatedFolder;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[folderState],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, [folderState]);
|
||||
|
||||
void onToggleSortOrder() {
|
||||
final newOrder = sortOrder.value == SortOrder.asc ? SortOrder.desc : SortOrder.asc;
|
||||
@@ -86,38 +77,19 @@ class FolderPage extends HookConsumerWidget {
|
||||
title: Text(currentFolder.value?.name ?? tr("folders")),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.swap_vert),
|
||||
onPressed: onToggleSortOrder,
|
||||
),
|
||||
],
|
||||
actions: [IconButton(icon: const Icon(Icons.swap_vert), onPressed: onToggleSortOrder)],
|
||||
),
|
||||
body: folderState.when(
|
||||
data: (rootFolder) {
|
||||
if (folder == null) {
|
||||
return FolderContent(
|
||||
folder: rootFolder,
|
||||
root: rootFolder,
|
||||
sortOrder: sortOrder.value,
|
||||
);
|
||||
return FolderContent(folder: rootFolder, root: rootFolder, sortOrder: sortOrder.value);
|
||||
} else {
|
||||
return FolderContent(
|
||||
folder: currentFolder.value!,
|
||||
root: rootFolder,
|
||||
sortOrder: sortOrder.value,
|
||||
);
|
||||
return FolderContent(folder: currentFolder.value!, root: rootFolder, sortOrder: sortOrder.value);
|
||||
}
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "failed_to_load_folder".tr(),
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
ImmichToast.show(context: context, msg: "failed_to_load_folder".tr(), toastType: ToastType.error);
|
||||
return Center(child: const Text("failed_to_load_folder").tr());
|
||||
},
|
||||
),
|
||||
@@ -130,26 +102,18 @@ class FolderContent extends HookConsumerWidget {
|
||||
final RootFolder root;
|
||||
final SortOrder sortOrder;
|
||||
|
||||
const FolderContent({
|
||||
super.key,
|
||||
this.folder,
|
||||
required this.root,
|
||||
this.sortOrder = SortOrder.asc,
|
||||
});
|
||||
const FolderContent({super.key, this.folder, required this.root, this.sortOrder = SortOrder.asc});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final folderRenderlist = ref.watch(folderRenderListProvider(folder!));
|
||||
|
||||
// Initial asset fetch
|
||||
useEffect(
|
||||
() {
|
||||
if (folder == null) return;
|
||||
ref.read(folderRenderListProvider(folder!).notifier).fetchAssets(sortOrder);
|
||||
return null;
|
||||
},
|
||||
[folder],
|
||||
);
|
||||
useEffect(() {
|
||||
if (folder == null) return;
|
||||
ref.read(folderRenderListProvider(folder!).notifier).fetchAssets(sortOrder);
|
||||
return null;
|
||||
}, [folder]);
|
||||
|
||||
if (folder == null) {
|
||||
return Center(child: const Text("folder_not_found").tr());
|
||||
@@ -182,18 +146,12 @@ class FolderContent extends HookConsumerWidget {
|
||||
if (folder!.subfolders.isNotEmpty)
|
||||
...folder!.subfolders.map(
|
||||
(subfolder) => LargeLeadingTile(
|
||||
leading: Icon(
|
||||
Icons.folder,
|
||||
color: context.primaryColor,
|
||||
size: 48,
|
||||
),
|
||||
leading: Icon(Icons.folder, color: context.primaryColor, size: 48),
|
||||
title: Text(
|
||||
subfolder.name,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: subfolder.subfolders.isNotEmpty
|
||||
? Text(
|
||||
@@ -212,23 +170,15 @@ class FolderContent extends HookConsumerWidget {
|
||||
onTap: () {
|
||||
ref.read(currentAssetProvider.notifier).set(asset);
|
||||
context.pushRoute(
|
||||
GalleryViewerRoute(
|
||||
renderList: list,
|
||||
initialIndex: list.allAssets!.indexOf(asset),
|
||||
),
|
||||
GalleryViewerRoute(renderList: list, initialIndex: list.allAssets!.indexOf(asset)),
|
||||
);
|
||||
},
|
||||
leading: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
child: SizedBox(
|
||||
width: 80,
|
||||
height: 80,
|
||||
child: ThumbnailImage(
|
||||
asset: asset,
|
||||
showStorageIndicator: false,
|
||||
),
|
||||
child: ThumbnailImage(asset: asset, showStorageIndicator: false),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
@@ -236,30 +186,20 @@ class FolderContent extends HookConsumerWidget {
|
||||
maxLines: 2,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
"${asset.exifInfo?.fileSize != null ? formatBytes(asset.exifInfo?.fileSize ?? 0) : ""} • ${DateFormat.yMMMd().format(asset.fileCreatedAt)}",
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "failed_to_load_assets".tr(),
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
ImmichToast.show(context: context, msg: "failed_to_load_assets".tr(), toastType: ToastType.error);
|
||||
return Center(child: const Text("failed_to_load_assets").tr());
|
||||
},
|
||||
),
|
||||
@@ -273,11 +213,7 @@ class FolderPath extends StatelessWidget {
|
||||
final RootFolder currentFolder;
|
||||
final RootFolder root;
|
||||
|
||||
const FolderPath({
|
||||
super.key,
|
||||
required this.currentFolder,
|
||||
required this.root,
|
||||
});
|
||||
const FolderPath({super.key, required this.currentFolder, required this.root});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -74,17 +74,11 @@ class LibraryPage extends ConsumerWidget {
|
||||
const Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
PeopleCollectionCard(),
|
||||
PlacesCollectionCard(),
|
||||
LocalAlbumsCollectionCard(),
|
||||
],
|
||||
children: [PeopleCollectionCard(), PlacesCollectionCard(), LocalAlbumsCollectionCard()],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const QuickAccessButtons(),
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -100,13 +94,8 @@ class QuickAccessButtons extends ConsumerWidget {
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.colorScheme.onSurface.withAlpha(10),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(color: context.colorScheme.onSurface.withAlpha(10), width: 1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(10),
|
||||
@@ -130,41 +119,26 @@ class QuickAccessButtons extends ConsumerWidget {
|
||||
bottomRight: Radius.circular(partners.isEmpty ? 20 : 0),
|
||||
),
|
||||
),
|
||||
leading: const Icon(
|
||||
Icons.folder_outlined,
|
||||
size: 26,
|
||||
),
|
||||
leading: const Icon(Icons.folder_outlined, size: 26),
|
||||
title: Text(
|
||||
IntlKeys.folders.tr(),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onTap: () => context.pushRoute(FolderRoute()),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.lock_outline_rounded,
|
||||
size: 26,
|
||||
),
|
||||
leading: const Icon(Icons.lock_outline_rounded, size: 26),
|
||||
title: Text(
|
||||
IntlKeys.locked_folder.tr(),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onTap: () => context.pushRoute(const LockedRoute()),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(
|
||||
Icons.group_outlined,
|
||||
size: 26,
|
||||
),
|
||||
leading: const Icon(Icons.group_outlined, size: 26),
|
||||
title: Text(
|
||||
IntlKeys.partners.tr(),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
onTap: () => context.pushRoute(const PartnerRoute()),
|
||||
),
|
||||
@@ -196,24 +170,13 @@ class PartnerList extends ConsumerWidget {
|
||||
bottomRight: Radius.circular(isLastItem ? 20 : 0),
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
right: 18.0,
|
||||
),
|
||||
contentPadding: const EdgeInsets.only(left: 12.0, right: 18.0),
|
||||
leading: userAvatar(context, partner, radius: 16),
|
||||
title: const Text(
|
||||
"partner_list_user_photos",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
).tr(
|
||||
namedArgs: {
|
||||
'user': partner.name,
|
||||
},
|
||||
),
|
||||
onTap: () => context.pushRoute(
|
||||
(PartnerDetailRoute(partner: partner)),
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
).tr(namedArgs: {'user': partner.name}),
|
||||
onTap: () => context.pushRoute((PartnerDetailRoute(partner: partner))),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -241,22 +204,15 @@ class PeopleCollectionCard extends ConsumerWidget {
|
||||
height: size,
|
||||
width: size,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(30),
|
||||
context.colorScheme.primary.withAlpha(25),
|
||||
],
|
||||
colors: [context.colorScheme.primary.withAlpha(30), context.colorScheme.primary.withAlpha(25)],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
child: people.widgetWhen(
|
||||
onLoading: () => const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
onLoading: () => const Center(child: CircularProgressIndicator()),
|
||||
onData: (people) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 2,
|
||||
@@ -308,9 +264,7 @@ class LocalAlbumsCollectionCard extends HookConsumerWidget {
|
||||
final size = context.width * widthFactor - 20.0;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(
|
||||
const LocalAlbumsRoute(),
|
||||
),
|
||||
onTap: () => context.pushRoute(const LocalAlbumsRoute()),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -321,10 +275,7 @@ class LocalAlbumsCollectionCard extends HookConsumerWidget {
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(30),
|
||||
context.colorScheme.primary.withAlpha(25),
|
||||
],
|
||||
colors: [context.colorScheme.primary.withAlpha(30), context.colorScheme.primary.withAlpha(25)],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
@@ -336,10 +287,7 @@ class LocalAlbumsCollectionCard extends HookConsumerWidget {
|
||||
mainAxisSpacing: 8,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: albums.take(4).map((album) {
|
||||
return AlbumThumbnailCard(
|
||||
album: album,
|
||||
showTitle: false,
|
||||
);
|
||||
return AlbumThumbnailCard(album: album, showTitle: false);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
@@ -373,11 +321,7 @@ class PlacesCollectionCard extends StatelessWidget {
|
||||
final size = context.width * widthFactor - 20.0;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(
|
||||
PlacesCollectionRoute(
|
||||
currentLocation: null,
|
||||
),
|
||||
),
|
||||
onTap: () => context.pushRoute(PlacesCollectionRoute(currentLocation: null)),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -392,10 +336,7 @@ class PlacesCollectionCard extends StatelessWidget {
|
||||
child: IgnorePointer(
|
||||
child: MapThumbnail(
|
||||
zoom: 8,
|
||||
centre: const LatLng(
|
||||
21.44950,
|
||||
-157.91959,
|
||||
),
|
||||
centre: const LatLng(21.44950, -157.91959),
|
||||
showAttribution: false,
|
||||
themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
),
|
||||
@@ -425,12 +366,7 @@ class ActionButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
||||
const ActionButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
required this.label,
|
||||
});
|
||||
const ActionButton({super.key, required this.onPressed, required this.icon, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -439,13 +375,7 @@ class ActionButton extends StatelessWidget {
|
||||
onPressed: onPressed,
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
child: Text(label, style: TextStyle(color: context.colorScheme.onSurface, fontSize: 15)),
|
||||
),
|
||||
style: FilledButton.styleFrom(
|
||||
elevation: 0,
|
||||
@@ -454,16 +384,10 @@ class ActionButton extends StatelessWidget {
|
||||
alignment: Alignment.centerLeft,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(25)),
|
||||
side: BorderSide(
|
||||
color: context.colorScheme.onSurface.withAlpha(10),
|
||||
width: 1,
|
||||
),
|
||||
side: BorderSide(color: context.colorScheme.onSurface.withAlpha(10), width: 1),
|
||||
),
|
||||
),
|
||||
icon: Icon(
|
||||
icon,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(icon, color: context.primaryColor),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@ class LocalAlbumsPage extends HookConsumerWidget {
|
||||
final albums = ref.watch(localAlbumsProvider);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('on_this_device'.tr()),
|
||||
),
|
||||
appBar: AppBar(title: Text('on_this_device'.tr())),
|
||||
body: ListView.builder(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
itemCount: albums.length,
|
||||
@@ -28,31 +26,18 @@ class LocalAlbumsPage extends HookConsumerWidget {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: LargeLeadingTile(
|
||||
leadingPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
),
|
||||
leadingPadding: const EdgeInsets.only(right: 16),
|
||||
leading: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
child: ImmichThumbnail(
|
||||
asset: albums[index].thumbnail.value,
|
||||
width: 80,
|
||||
height: 80,
|
||||
),
|
||||
child: ImmichThumbnail(asset: albums[index].thumbnail.value, width: 80, height: 80),
|
||||
),
|
||||
title: Text(
|
||||
albums[index].name,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
subtitle: Text(
|
||||
'items_count'.t(
|
||||
context: context,
|
||||
args: {'count': albums[index].assetCount},
|
||||
),
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
'items_count'.t(context: context, args: {'count': albums[index].assetCount}),
|
||||
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
onTap: () => context.pushRoute(AlbumViewerRoute(albumId: albums[index].id)),
|
||||
),
|
||||
|
||||
@@ -19,29 +19,23 @@ class LockedPage extends HookConsumerWidget {
|
||||
final showOverlay = useState(false);
|
||||
final authProviderNotifier = ref.read(authProvider.notifier);
|
||||
// lock the page when it is destroyed
|
||||
useEffect(
|
||||
() {
|
||||
return () {
|
||||
authProviderNotifier.lockPinCode();
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
return () {
|
||||
authProviderNotifier.lockPinCode();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (context.mounted) {
|
||||
if (appLifeCycle == AppLifecycleState.resumed) {
|
||||
showOverlay.value = false;
|
||||
} else {
|
||||
showOverlay.value = true;
|
||||
}
|
||||
useEffect(() {
|
||||
if (context.mounted) {
|
||||
if (appLifeCycle == AppLifecycleState.resumed) {
|
||||
showOverlay.value = false;
|
||||
} else {
|
||||
showOverlay.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[appLifeCycle],
|
||||
);
|
||||
return null;
|
||||
}, [appLifeCycle]);
|
||||
|
||||
return Scaffold(
|
||||
appBar: ref.watch(multiselectProvider) ? null : const LockPageAppBar(),
|
||||
@@ -51,12 +45,7 @@ class LockedPage extends HookConsumerWidget {
|
||||
renderListProvider: lockedTimelineProvider,
|
||||
topWidget: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'no_locked_photos_message'.tr(),
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
child: Center(child: Text('no_locked_photos_message'.tr(), style: context.textTheme.labelLarge)),
|
||||
),
|
||||
editEnabled: false,
|
||||
favoriteEnabled: false,
|
||||
@@ -84,9 +73,7 @@ class LockPageAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
title: const Text(
|
||||
'locked_folder',
|
||||
).tr(),
|
||||
title: const Text('locked_folder').tr(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,18 +23,12 @@ class PinAuthPage extends HookConsumerWidget {
|
||||
final isBetaTimeline = Store.isBetaTimelineEnabled;
|
||||
|
||||
Future<void> registerBiometric(String pinCode) async {
|
||||
final isRegistered = await ref.read(localAuthProvider.notifier).registerBiometric(
|
||||
context,
|
||||
pinCode,
|
||||
);
|
||||
final isRegistered = await ref.read(localAuthProvider.notifier).registerBiometric(context, pinCode);
|
||||
|
||||
if (isRegistered) {
|
||||
context.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'biometric_auth_enabled'.tr(),
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
content: Text('biometric_auth_enabled'.tr(), style: context.textTheme.labelLarge),
|
||||
duration: const Duration(seconds: 3),
|
||||
backgroundColor: context.colorScheme.primaryContainer,
|
||||
),
|
||||
@@ -79,20 +73,14 @@ class PinAuthPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('locked_folder'.tr()),
|
||||
),
|
||||
appBar: AppBar(title: Text('locked_folder'.tr())),
|
||||
body: ListView(
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 36.0),
|
||||
child: showPinRegistrationForm.value
|
||||
? Center(
|
||||
child: PinRegistrationForm(
|
||||
onDone: () => showPinRegistrationForm.value = false,
|
||||
),
|
||||
)
|
||||
? Center(child: PinRegistrationForm(onDone: () => showPinRegistrationForm.value = false))
|
||||
: Column(
|
||||
children: [
|
||||
Center(
|
||||
@@ -112,17 +100,11 @@ class PinAuthPage extends HookConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(
|
||||
Icons.fingerprint,
|
||||
size: 28,
|
||||
),
|
||||
icon: const Icon(Icons.fingerprint, size: 28),
|
||||
onPressed: enableBiometricAuth,
|
||||
label: Text(
|
||||
'use_biometric'.tr(),
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
fontSize: 18,
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor, fontSize: 18),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -22,10 +22,7 @@ class DriftPartnerPage extends HookConsumerWidget {
|
||||
addNewUsersHandler() async {
|
||||
final potentialPartners = potentialPartnersAsync.value;
|
||||
if (potentialPartners == null || potentialPartners.isEmpty) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "partner_page_no_more_users".tr(),
|
||||
);
|
||||
ImmichToast.show(context: context, msg: "partner_page_no_more_users".tr());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -77,18 +74,13 @@ class DriftPartnerPage extends HookConsumerWidget {
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: potentialPartnersAsync.whenOrNull(
|
||||
data: (data) => addNewUsersHandler,
|
||||
),
|
||||
onPressed: potentialPartnersAsync.whenOrNull(data: (data) => addNewUsersHandler),
|
||||
icon: const Icon(Icons.person_add),
|
||||
tooltip: "add_partner".tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _SharedToPartnerList(
|
||||
onAddPartner: addNewUsersHandler,
|
||||
onDeletePartner: onDeleteUser,
|
||||
),
|
||||
body: _SharedToPartnerList(onAddPartner: addNewUsersHandler, onDeletePartner: onDeleteUser),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -97,10 +89,7 @@ class _SharedToPartnerList extends ConsumerWidget {
|
||||
final VoidCallback onAddPartner;
|
||||
final Function(PartnerUserDto partner) onDeletePartner;
|
||||
|
||||
const _SharedToPartnerList({
|
||||
required this.onAddPartner,
|
||||
required this.onDeletePartner,
|
||||
});
|
||||
const _SharedToPartnerList({required this.onAddPartner, required this.onDeletePartner});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -116,10 +105,7 @@ class _SharedToPartnerList extends ConsumerWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: const Text(
|
||||
"partner_page_empty_message",
|
||||
style: TextStyle(fontSize: 14),
|
||||
).tr(),
|
||||
child: const Text("partner_page_empty_message", style: TextStyle(fontSize: 14)).tr(),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
@@ -142,18 +128,13 @@ class _SharedToPartnerList extends ConsumerWidget {
|
||||
leading: PartnerUserAvatar(partner: partner),
|
||||
title: Text(partner.name),
|
||||
subtitle: Text(partner.email),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.person_remove),
|
||||
onPressed: () => onDeletePartner(partner),
|
||||
),
|
||||
trailing: IconButton(icon: const Icon(Icons.person_remove), onPressed: () => onDeletePartner(partner)),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) => Center(
|
||||
child: Text("Error loading partners: $error"),
|
||||
),
|
||||
error: (error, stack) => Center(child: Text("Error loading partners: $error")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,7 @@ class PartnerPage extends HookConsumerWidget {
|
||||
addNewUsersHandler() async {
|
||||
final users = availableUsers.value;
|
||||
if (users == null || users.isEmpty) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "partner_page_no_more_users".tr(),
|
||||
);
|
||||
ImmichToast.show(context: context, msg: "partner_page_no_more_users".tr());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,10 +37,7 @@ class PartnerPage extends HookConsumerWidget {
|
||||
onPressed: () => context.pop(u),
|
||||
child: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: userAvatar(context, u),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.only(right: 8), child: userAvatar(context, u)),
|
||||
Text(u.name),
|
||||
],
|
||||
),
|
||||
@@ -57,11 +51,7 @@ class PartnerPage extends HookConsumerWidget {
|
||||
if (ok) {
|
||||
ref.invalidate(partnerSharedByProvider);
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "partner_page_partner_add_failed".tr(),
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
ImmichToast.show(context: context, msg: "partner_page_partner_add_failed".tr(), toastType: ToastType.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,9 +77,7 @@ class PartnerPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
||||
child: Text(
|
||||
"partner_page_shared_to_title",
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface.withAlpha(200),
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)),
|
||||
).tr(),
|
||||
),
|
||||
if (users.isNotEmpty)
|
||||
@@ -99,10 +87,7 @@ class PartnerPage extends HookConsumerWidget {
|
||||
itemBuilder: ((context, index) {
|
||||
return ListTile(
|
||||
leading: userAvatar(context, users[index]),
|
||||
title: Text(
|
||||
users[index].email,
|
||||
style: context.textTheme.bodyLarge,
|
||||
),
|
||||
title: Text(users[index].email, style: context.textTheme.bodyLarge),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.person_remove),
|
||||
onPressed: () => onDeleteUser(users[index]),
|
||||
@@ -118,17 +103,12 @@ class PartnerPage extends HookConsumerWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: const Text(
|
||||
"partner_page_empty_message",
|
||||
style: TextStyle(fontSize: 14),
|
||||
).tr(),
|
||||
child: const Text("partner_page_empty_message", style: TextStyle(fontSize: 14)).tr(),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: availableUsers.whenOrNull(
|
||||
data: (data) => addNewUsersHandler,
|
||||
),
|
||||
onPressed: availableUsers.whenOrNull(data: (data) => addNewUsersHandler),
|
||||
icon: const Icon(Icons.person_add),
|
||||
label: const Text("add_partner").tr(),
|
||||
),
|
||||
|
||||
@@ -22,24 +22,18 @@ class PartnerDetailPage extends HookConsumerWidget {
|
||||
final inTimeline = useState(partner.inTimeline);
|
||||
bool toggleInProcess = false;
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
Future.microtask(
|
||||
() async => {
|
||||
await ref.read(assetProvider.notifier).getAllAsset(),
|
||||
},
|
||||
);
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
Future.microtask(() async => {await ref.read(assetProvider.notifier).getAllAsset()});
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
void toggleInTimeline() async {
|
||||
if (toggleInProcess) return;
|
||||
toggleInProcess = true;
|
||||
try {
|
||||
final ok =
|
||||
await ref.read(partnerSharedWithProvider.notifier).updatePartner(partner, inTimeline: !inTimeline.value);
|
||||
final ok = await ref
|
||||
.read(partnerSharedWithProvider.notifier)
|
||||
.updatePartner(partner, inTimeline: !inTimeline.value);
|
||||
if (ok) {
|
||||
inTimeline.value = !inTimeline.value;
|
||||
final action = inTimeline.value ? "shown on" : "hidden from";
|
||||
@@ -65,28 +59,16 @@ class PartnerDetailPage extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
appBar: ref.watch(multiselectProvider)
|
||||
? null
|
||||
: AppBar(
|
||||
title: Text(partner.name),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
),
|
||||
: AppBar(title: Text(partner.name), elevation: 0, centerTitle: false),
|
||||
body: MultiselectGrid(
|
||||
topWidget: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 16.0),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.colorScheme.onSurface.withAlpha(10),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(color: context.colorScheme.onSurface.withAlpha(10), width: 1),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(10),
|
||||
context.colorScheme.primary.withAlpha(15),
|
||||
],
|
||||
colors: [context.colorScheme.primary.withAlpha(10), context.colorScheme.primary.withAlpha(15)],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
@@ -96,18 +78,13 @@ class PartnerDetailPage extends HookConsumerWidget {
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
"Show in timeline",
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(color: context.colorScheme.primary),
|
||||
),
|
||||
subtitle: Text(
|
||||
"Show photos and videos from this user in your timeline",
|
||||
style: context.textTheme.bodyMedium,
|
||||
),
|
||||
trailing: Switch(
|
||||
value: inTimeline.value,
|
||||
onChanged: (_) => toggleInTimeline(),
|
||||
),
|
||||
trailing: Switch(value: inTimeline.value, onChanged: (_) => toggleInTimeline()),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -21,10 +21,7 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
final formFocus = useFocusNode();
|
||||
final ValueNotifier<String?> search = useState(null);
|
||||
|
||||
showNameEditModel(
|
||||
String personId,
|
||||
String personName,
|
||||
) {
|
||||
showNameEditModel(String personId, String personName) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
@@ -84,22 +81,14 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.pushRoute(
|
||||
PersonResultRoute(
|
||||
personId: person.id,
|
||||
personName: person.name,
|
||||
),
|
||||
);
|
||||
context.pushRoute(PersonResultRoute(personId: person.id, personName: person.name));
|
||||
},
|
||||
child: Material(
|
||||
shape: const CircleBorder(side: BorderSide.none),
|
||||
elevation: 3,
|
||||
child: CircleAvatar(
|
||||
maxRadius: isTablet ? 120 / 2 : 96 / 2,
|
||||
backgroundImage: NetworkImage(
|
||||
getFaceThumbnailUrl(person.id),
|
||||
headers: headers,
|
||||
),
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(person.id), headers: headers),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -115,15 +104,11 @@ class PeopleCollectionPage extends HookConsumerWidget {
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
person.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -61,11 +61,7 @@ class PlacesCollectionPage extends HookConsumerWidget {
|
||||
child: MapThumbnail(
|
||||
onTap: (_, __) => context.pushRoute(MapRoute(initialLocation: currentLocation)),
|
||||
zoom: 8,
|
||||
centre: currentLocation ??
|
||||
const LatLng(
|
||||
21.44950,
|
||||
-157.91959,
|
||||
),
|
||||
centre: currentLocation ?? const LatLng(21.44950, -157.91959),
|
||||
showAttribution: false,
|
||||
themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
),
|
||||
@@ -113,16 +109,10 @@ class PlaceTile extends StatelessWidget {
|
||||
SearchRoute(
|
||||
prefilter: SearchFilter(
|
||||
people: {},
|
||||
location: SearchLocationFilter(
|
||||
city: name,
|
||||
),
|
||||
location: SearchLocationFilter(city: name),
|
||||
camera: SearchCameraFilter(),
|
||||
date: SearchDateFilter(),
|
||||
display: SearchDisplayFilters(
|
||||
isNotInAlbum: false,
|
||||
isArchive: false,
|
||||
isFavorite: false,
|
||||
),
|
||||
display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false),
|
||||
mediaType: AssetType.other,
|
||||
),
|
||||
),
|
||||
@@ -131,16 +121,9 @@ class PlaceTile extends StatelessWidget {
|
||||
|
||||
return LargeLeadingTile(
|
||||
onTap: () => navigateToPlace(),
|
||||
title: Text(
|
||||
name,
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
title: Text(name, style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500)),
|
||||
leading: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
child: CachedNetworkImage(
|
||||
width: 80,
|
||||
height: 80,
|
||||
|
||||
@@ -17,16 +17,13 @@ class SharedLinkPage extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sharedLinks = ref.watch(sharedLinksStateProvider);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
ref.read(sharedLinksStateProvider.notifier).fetchLinks();
|
||||
return () {
|
||||
if (!context.mounted) return;
|
||||
ref.invalidate(sharedLinksStateProvider);
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
ref.read(sharedLinksStateProvider.notifier).fetchLinks();
|
||||
return () {
|
||||
if (!context.mounted) return;
|
||||
ref.invalidate(sharedLinksStateProvider);
|
||||
};
|
||||
}, []);
|
||||
|
||||
Widget buildNoShares() {
|
||||
return Column(
|
||||
@@ -36,30 +33,19 @@ class SharedLinkPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
|
||||
child: const Text(
|
||||
"shared_link_manage_links",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: const Text(
|
||||
"you_dont_have_any_shared_links",
|
||||
style: TextStyle(fontSize: 14),
|
||||
).tr(),
|
||||
child: const Text("you_dont_have_any_shared_links", style: TextStyle(fontSize: 14)).tr(),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.link_off,
|
||||
size: 100,
|
||||
color: context.themeData.iconTheme.color?.withValues(alpha: 0.5),
|
||||
),
|
||||
child: Icon(Icons.link_off, size: 100, color: context.themeData.iconTheme.color?.withValues(alpha: 0.5)),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -74,9 +60,7 @@ class SharedLinkPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 16.0, bottom: 30.0),
|
||||
child: Text(
|
||||
"shared_link_manage_links",
|
||||
style: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.textTheme.labelLarge?.color?.withAlpha(200),
|
||||
),
|
||||
style: context.textTheme.labelLarge?.copyWith(color: context.textTheme.labelLarge?.color?.withAlpha(200)),
|
||||
).tr(),
|
||||
),
|
||||
Expanded(
|
||||
@@ -111,11 +95,7 @@ class SharedLinkPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("shared_link_app_bar_title").tr(),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
),
|
||||
appBar: AppBar(title: const Text("shared_link_app_bar_title").tr(), elevation: 0, centerTitle: false),
|
||||
body: SafeArea(
|
||||
child: sharedLinks.widgetWhen(
|
||||
onError: (error, stackTrace) => buildNoShares(),
|
||||
|
||||
@@ -19,12 +19,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
final List<String>? assetsList;
|
||||
final String? albumId;
|
||||
|
||||
const SharedLinkEditPage({
|
||||
super.key,
|
||||
this.existingLink,
|
||||
this.assetsList,
|
||||
this.albumId,
|
||||
});
|
||||
const SharedLinkEditPage({super.key, this.existingLink, this.assetsList, this.albumId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -46,20 +41,11 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
if (existingLink!.type == SharedLinkSource.album) {
|
||||
return Row(
|
||||
children: [
|
||||
const Text(
|
||||
'public_album',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
const Text(
|
||||
" | ",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Text('public_album', style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Text(
|
||||
existingLink!.title,
|
||||
style: TextStyle(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -68,21 +54,12 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
if (existingLink!.type == SharedLinkSource.individual) {
|
||||
return Row(
|
||||
children: [
|
||||
const Text(
|
||||
'shared_link_individual_shared',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
const Text(
|
||||
" | ",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const Text('shared_link_individual_shared', style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
existingLink!.description ?? "--",
|
||||
style: TextStyle(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
@@ -91,10 +68,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
return const Text(
|
||||
"create_link_to_share_description",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr();
|
||||
return const Text("create_link_to_share_description", style: TextStyle(fontWeight: FontWeight.bold)).tr();
|
||||
}
|
||||
|
||||
Widget buildDescriptionField() {
|
||||
@@ -106,20 +80,12 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
autofocus: false,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'description'.tr(),
|
||||
labelStyle: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'shared_link_edit_description_hint'.tr(),
|
||||
hintStyle: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
),
|
||||
disabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5)),
|
||||
),
|
||||
hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14),
|
||||
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))),
|
||||
),
|
||||
onTapOutside: (_) => descriptionFocusNode.unfocus(),
|
||||
);
|
||||
@@ -132,20 +98,12 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
autofocus: false,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'password'.tr(),
|
||||
labelStyle: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
border: const OutlineInputBorder(),
|
||||
hintText: 'shared_link_edit_password_hint'.tr(),
|
||||
hintStyle: const TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
),
|
||||
disabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5)),
|
||||
),
|
||||
hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14),
|
||||
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -156,10 +114,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
onChanged: newShareLink.value.isEmpty ? (value) => showMetadata.value = value : null,
|
||||
activeColor: colorScheme.primary,
|
||||
dense: true,
|
||||
title: Text(
|
||||
"show_metadata",
|
||||
style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
title: Text("show_metadata", style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold)).tr(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,10 +161,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
return DropdownMenu(
|
||||
label: Text(
|
||||
"expire_after",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary),
|
||||
).tr(),
|
||||
enableSearch: false,
|
||||
enableFilter: false,
|
||||
@@ -220,26 +172,17 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
expiryAfter.value = value!;
|
||||
},
|
||||
dropdownMenuEntries: [
|
||||
DropdownMenuEntry(
|
||||
value: 0,
|
||||
label: "never".tr(),
|
||||
),
|
||||
DropdownMenuEntry(value: 0, label: "never".tr()),
|
||||
DropdownMenuEntry(
|
||||
value: 30,
|
||||
label: "shared_link_edit_expire_after_option_minutes".tr(namedArgs: {'count': "30"}),
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: 60,
|
||||
label: "shared_link_edit_expire_after_option_hour".tr(),
|
||||
),
|
||||
DropdownMenuEntry(value: 60, label: "shared_link_edit_expire_after_option_hour".tr()),
|
||||
DropdownMenuEntry(
|
||||
value: 60 * 6,
|
||||
label: "shared_link_edit_expire_after_option_hours".tr(namedArgs: {'count': "6"}),
|
||||
),
|
||||
DropdownMenuEntry(
|
||||
value: 60 * 24,
|
||||
label: "shared_link_edit_expire_after_option_day".tr(),
|
||||
),
|
||||
DropdownMenuEntry(value: 60 * 24, label: "shared_link_edit_expire_after_option_day".tr()),
|
||||
DropdownMenuEntry(
|
||||
value: 60 * 24 * 7,
|
||||
label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "7"}),
|
||||
@@ -266,9 +209,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"shared_link_clipboard_copied_massage",
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
).tr(),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
@@ -279,23 +220,14 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
Widget buildNewLinkField() {
|
||||
return Column(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 20,
|
||||
bottom: 20,
|
||||
),
|
||||
child: Divider(),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 20, bottom: 20), child: Divider()),
|
||||
TextFormField(
|
||||
readOnly: true,
|
||||
initialValue: newShareLink.value,
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
enabledBorder: themeData.inputDecorationTheme.focusedBorder,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: copyLinkToClipboard,
|
||||
icon: const Icon(Icons.copy),
|
||||
),
|
||||
suffixIcon: IconButton(onPressed: copyLinkToClipboard, icon: const Icon(Icons.copy)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
@@ -306,13 +238,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
onPressed: () {
|
||||
context.maybePop();
|
||||
},
|
||||
child: const Text(
|
||||
"done",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
child: const Text("done", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -325,7 +251,9 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
Future<void> handleNewLink() async {
|
||||
final newLink = await ref.read(sharedLinkServiceProvider).createSharedLink(
|
||||
final newLink = await ref
|
||||
.read(sharedLinkServiceProvider)
|
||||
.createSharedLink(
|
||||
albumId: albumId,
|
||||
assetIds: assetsList,
|
||||
showMeta: showMetadata.value,
|
||||
@@ -336,9 +264,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(),
|
||||
);
|
||||
ref.invalidate(sharedLinksStateProvider);
|
||||
final externalDomain = ref.read(
|
||||
serverInfoProvider.select((s) => s.serverConfig.externalDomain),
|
||||
);
|
||||
final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain));
|
||||
var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl();
|
||||
if (serverUrl != null && !serverUrl.endsWith('/')) {
|
||||
serverUrl += '/';
|
||||
@@ -390,7 +316,9 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
changeExpiry = true;
|
||||
}
|
||||
|
||||
await ref.read(sharedLinkServiceProvider).updateSharedLink(
|
||||
await ref
|
||||
.read(sharedLinkServiceProvider)
|
||||
.updateSharedLink(
|
||||
existingLink!.id,
|
||||
showMeta: meta,
|
||||
allowDownload: download,
|
||||
@@ -406,9 +334,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
existingLink == null ? "create_link_to_share" : "edit_link",
|
||||
).tr(),
|
||||
title: Text(existingLink == null ? "create_link_to_share" : "edit_link").tr(),
|
||||
elevation: 0,
|
||||
leading: const CloseButton(),
|
||||
centerTitle: false,
|
||||
@@ -416,32 +342,15 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(padding: const EdgeInsets.all(padding), child: buildLinkTitle()),
|
||||
Padding(padding: const EdgeInsets.all(padding), child: buildDescriptionField()),
|
||||
Padding(padding: const EdgeInsets.all(padding), child: buildPasswordField()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(padding),
|
||||
child: buildLinkTitle(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(padding),
|
||||
child: buildDescriptionField(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(padding),
|
||||
child: buildPasswordField(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: padding,
|
||||
right: padding,
|
||||
bottom: padding,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding),
|
||||
child: buildShowMetaButton(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: padding,
|
||||
right: padding,
|
||||
bottom: padding,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding),
|
||||
child: buildAllowDownloadButton(),
|
||||
),
|
||||
Padding(
|
||||
@@ -450,48 +359,30 @@ class SharedLinkEditPage extends HookConsumerWidget {
|
||||
),
|
||||
if (existingLink != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: padding,
|
||||
right: padding,
|
||||
bottom: padding,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding),
|
||||
child: buildEditExpiryButton(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: padding,
|
||||
right: padding,
|
||||
bottom: padding,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding),
|
||||
child: buildExpiryAfterButton(),
|
||||
),
|
||||
if (newShareLink.value.isEmpty)
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: padding + 10,
|
||||
bottom: padding,
|
||||
),
|
||||
padding: const EdgeInsets.only(right: padding + 10, bottom: padding),
|
||||
child: ElevatedButton(
|
||||
onPressed: existingLink != null ? handleEditLink : handleNewLink,
|
||||
child: Text(
|
||||
existingLink != null ? "shared_link_edit_submit_button" : "create_link",
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (newShareLink.value.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: padding,
|
||||
right: padding,
|
||||
bottom: padding,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding),
|
||||
child: buildNewLinkField(),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -29,10 +29,7 @@ class TrashPage extends HookConsumerWidget {
|
||||
final selection = useState(<Asset>{});
|
||||
final processing = useProcessingOverlay();
|
||||
|
||||
void selectionListener(
|
||||
bool multiselect,
|
||||
Set<Asset> selectedAssets,
|
||||
) {
|
||||
void selectionListener(bool multiselect, Set<Asset> selectedAssets) {
|
||||
selectionEnabledHook.value = multiselect;
|
||||
selection.value = selectedAssets;
|
||||
}
|
||||
@@ -43,11 +40,7 @@ class TrashPage extends HookConsumerWidget {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'trash_emptied'.tr(),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
ImmichToast.show(context: context, msg: 'trash_emptied'.tr(), gravity: ToastGravity.BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +81,7 @@ class TrashPage extends HookConsumerWidget {
|
||||
handlePermanentDelete() async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => DeleteDialog(
|
||||
alert: "delete_dialog_alert_remote",
|
||||
onDelete: () => onPermanentlyDelete(),
|
||||
),
|
||||
builder: (context) => DeleteDialog(alert: "delete_dialog_alert_remote", onDelete: () => onPermanentlyDelete()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,8 +128,9 @@ class TrashPage extends HookConsumerWidget {
|
||||
selectionEnabledHook.value = false;
|
||||
selection.value = {};
|
||||
},
|
||||
icon:
|
||||
!selectionEnabledHook.value ? const Icon(Icons.arrow_back_ios_rounded) : const Icon(Icons.close_rounded),
|
||||
icon: !selectionEnabledHook.value
|
||||
? const Icon(Icons.arrow_back_ios_rounded)
|
||||
: const Icon(Icons.close_rounded),
|
||||
),
|
||||
centerTitle: !selectionEnabledHook.value,
|
||||
automaticallyImplyLeading: false,
|
||||
@@ -149,14 +140,8 @@ class TrashPage extends HookConsumerWidget {
|
||||
PopupMenuButton<void Function()>(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
value: () => selectionEnabledHook.value = true,
|
||||
child: const Text('select').tr(),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: handleEmptyTrash,
|
||||
child: const Text('empty_trash').tr(),
|
||||
),
|
||||
PopupMenuItem(value: () => selectionEnabledHook.value = true, child: const Text('select').tr()),
|
||||
PopupMenuItem(value: handleEmptyTrash, child: const Text('empty_trash').tr()),
|
||||
];
|
||||
},
|
||||
onSelected: (fn) => fn(),
|
||||
@@ -177,40 +162,28 @@ class TrashPage extends HookConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
icon: Icon(
|
||||
Icons.delete_forever,
|
||||
color: Colors.red[400],
|
||||
),
|
||||
icon: Icon(Icons.delete_forever, color: Colors.red[400]),
|
||||
label: Text(
|
||||
selection.value.isEmpty ? 'trash_page_delete_all'.tr() : 'delete'.tr(),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.red[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 14, color: Colors.red[400], fontWeight: FontWeight.bold),
|
||||
),
|
||||
onPressed: processing.value
|
||||
? null
|
||||
: selection.value.isEmpty
|
||||
? handleEmptyTrash
|
||||
: handlePermanentDelete,
|
||||
? handleEmptyTrash
|
||||
: handlePermanentDelete,
|
||||
),
|
||||
TextButton.icon(
|
||||
icon: const Icon(
|
||||
Icons.history_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.history_rounded),
|
||||
label: Text(
|
||||
selection.value.isEmpty ? 'trash_page_restore_all'.tr() : 'restore'.tr(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
onPressed: processing.value
|
||||
? null
|
||||
: selection.value.isEmpty
|
||||
? handleRestoreAll
|
||||
: handleRestore,
|
||||
? handleRestoreAll
|
||||
: handleRestore,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -227,9 +200,7 @@ class TrashPage extends HookConsumerWidget {
|
||||
),
|
||||
body: trashRenderList.widgetWhen(
|
||||
onData: (data) => data.isEmpty
|
||||
? Center(
|
||||
child: Text('trash_page_no_assets'.tr()),
|
||||
)
|
||||
? Center(child: Text('trash_page_no_assets'.tr()))
|
||||
: Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
@@ -240,13 +211,8 @@ class TrashPage extends HookConsumerWidget {
|
||||
showMultiSelectIndicator: false,
|
||||
showStack: true,
|
||||
topWidget: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 24,
|
||||
),
|
||||
child: const Text(
|
||||
"trash_page_info",
|
||||
).tr(namedArgs: {"days": "$trashDays"}),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 24),
|
||||
child: const Text("trash_page_info").tr(namedArgs: {"days": "$trashDays"}),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -9,8 +9,6 @@ class ChangePasswordPage extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const Scaffold(
|
||||
body: ChangePasswordForm(),
|
||||
);
|
||||
return const Scaffold(body: ChangePasswordForm());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,10 @@ class LoginPage extends HookConsumerWidget {
|
||||
appVersion.value = packageInfo.version;
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
getAppInfo();
|
||||
return null;
|
||||
},
|
||||
);
|
||||
useEffect(() {
|
||||
getAppInfo();
|
||||
return null;
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
body: LoginForm(),
|
||||
|
||||
@@ -26,24 +26,18 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'permission_onboarding_request',
|
||||
style: context.textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
).tr(),
|
||||
Text('permission_onboarding_request', style: context.textTheme.titleMedium, textAlign: TextAlign.center).tr(),
|
||||
const SizedBox(height: 18),
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission().then((permission) async {
|
||||
if (permission.isGranted) {
|
||||
// If permission is limited, we will show the limited
|
||||
// permission page
|
||||
goToBackup();
|
||||
}
|
||||
}),
|
||||
child: const Text(
|
||||
'continue',
|
||||
).tr(),
|
||||
if (permission.isGranted) {
|
||||
// If permission is limited, we will show the limited
|
||||
// permission page
|
||||
goToBackup();
|
||||
}
|
||||
}),
|
||||
child: const Text('continue').tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -62,10 +56,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
textAlign: TextAlign.center,
|
||||
).tr(),
|
||||
const SizedBox(height: 18),
|
||||
ElevatedButton(
|
||||
onPressed: () => goToBackup(),
|
||||
child: const Text('permission_onboarding_get_started').tr(),
|
||||
),
|
||||
ElevatedButton(onPressed: () => goToBackup(), child: const Text('permission_onboarding_get_started').tr()),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -78,11 +69,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.warning_outlined,
|
||||
color: Colors.yellow,
|
||||
size: 48,
|
||||
),
|
||||
const Icon(Icons.warning_outlined, color: Colors.yellow, size: 48),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'permission_onboarding_permission_limited',
|
||||
@@ -92,17 +79,10 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
const SizedBox(height: 18),
|
||||
ElevatedButton(
|
||||
onPressed: () => openAppSettings(),
|
||||
child: const Text(
|
||||
'permission_onboarding_go_to_settings',
|
||||
).tr(),
|
||||
child: const Text('permission_onboarding_go_to_settings').tr(),
|
||||
),
|
||||
const SizedBox(height: 8.0),
|
||||
TextButton(
|
||||
onPressed: () => goToBackup(),
|
||||
child: const Text(
|
||||
'permission_onboarding_continue_anyway',
|
||||
).tr(),
|
||||
),
|
||||
TextButton(onPressed: () => goToBackup(), child: const Text('permission_onboarding_continue_anyway').tr()),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -112,11 +92,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.warning_outlined,
|
||||
color: Colors.red,
|
||||
size: 48,
|
||||
),
|
||||
const Icon(Icons.warning_outlined, color: Colors.red, size: 48),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'permission_onboarding_permission_denied',
|
||||
@@ -126,9 +102,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
const SizedBox(height: 18),
|
||||
ElevatedButton(
|
||||
onPressed: () => openAppSettings(),
|
||||
child: const Text(
|
||||
'permission_onboarding_go_to_settings',
|
||||
).tr(),
|
||||
child: const Text('permission_onboarding_go_to_settings').tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -138,7 +112,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
PermissionStatus.limited => buildPermissionLimited(),
|
||||
PermissionStatus.denied => buildRequestPermission(),
|
||||
PermissionStatus.granted || PermissionStatus.provisional => buildPermissionGranted(),
|
||||
PermissionStatus.restricted || PermissionStatus.permanentlyDenied => buildPermissionDenied()
|
||||
PermissionStatus.restricted || PermissionStatus.permanentlyDenied => buildPermissionDenied(),
|
||||
};
|
||||
|
||||
return Scaffold(
|
||||
@@ -150,21 +124,13 @@ class PermissionOnboardingPage extends HookConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const ImmichLogo(
|
||||
heroTag: 'logo',
|
||||
),
|
||||
const ImmichLogo(heroTag: 'logo'),
|
||||
const ImmichTitleText(),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('back').tr(),
|
||||
onPressed: () => context.maybePop(),
|
||||
child: Padding(padding: const EdgeInsets.all(18.0), child: child),
|
||||
),
|
||||
TextButton(child: const Text('back').tr(), onPressed: () => context.maybePop()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -16,26 +16,19 @@ import 'package:immich_mobile/widgets/memories/memory_epilogue.dart';
|
||||
import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart';
|
||||
|
||||
@RoutePage()
|
||||
|
||||
/// Expects [currentAssetProvider] to be set before navigating to this page
|
||||
class MemoryPage extends HookConsumerWidget {
|
||||
final List<Memory> memories;
|
||||
final int memoryIndex;
|
||||
|
||||
const MemoryPage({
|
||||
required this.memories,
|
||||
required this.memoryIndex,
|
||||
super.key,
|
||||
});
|
||||
const MemoryPage({required this.memories, required this.memoryIndex, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final currentMemory = useState(memories[memoryIndex]);
|
||||
final currentAssetPage = useState(0);
|
||||
final currentMemoryIndex = useState(memoryIndex);
|
||||
final assetProgress = useState(
|
||||
"${currentAssetPage.value + 1}|${currentMemory.value.assets.length}",
|
||||
);
|
||||
final assetProgress = useState("${currentAssetPage.value + 1}|${currentMemory.value.assets.length}");
|
||||
const bgColor = Colors.black;
|
||||
final currentAsset = useState<Asset?>(null);
|
||||
|
||||
@@ -55,19 +48,13 @@ class MemoryPage extends HookConsumerWidget {
|
||||
});
|
||||
|
||||
toNextMemory() {
|
||||
memoryPageController.nextPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
memoryPageController.nextPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn);
|
||||
}
|
||||
|
||||
void toPreviousMemory() {
|
||||
if (currentMemoryIndex.value > 0) {
|
||||
// Move to the previous memory page
|
||||
memoryPageController.previousPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeIn,
|
||||
);
|
||||
memoryPageController.previousPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn);
|
||||
|
||||
// Wait for the next frame to ensure the page is built
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -94,10 +81,7 @@ class MemoryPage extends HookConsumerWidget {
|
||||
// Go to the next asset
|
||||
PageController controller = memoryAssetPageControllers[currentMemoryIndex.value];
|
||||
|
||||
controller.nextPage(
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
);
|
||||
controller.nextPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500));
|
||||
} else {
|
||||
// Go to the next memory since we are at the end of our assets
|
||||
toNextMemory();
|
||||
@@ -109,10 +93,7 @@ class MemoryPage extends HookConsumerWidget {
|
||||
// Go to the previous asset
|
||||
PageController controller = memoryAssetPageControllers[currentMemoryIndex.value];
|
||||
|
||||
controller.previousPage(
|
||||
curve: Curves.easeInOut,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
);
|
||||
controller.previousPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500));
|
||||
} else {
|
||||
// Go to the previous memory since we are at the end of our assets
|
||||
toPreviousMemory();
|
||||
@@ -161,11 +142,7 @@ class MemoryPage extends HookConsumerWidget {
|
||||
// Precache the asset
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
await precacheImage(
|
||||
ImmichImage.imageProvider(
|
||||
asset: asset,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
),
|
||||
ImmichImage.imageProvider(asset: asset, width: size.width, height: size.height),
|
||||
context,
|
||||
size: size,
|
||||
);
|
||||
@@ -219,9 +196,7 @@ class MemoryPage extends HookConsumerWidget {
|
||||
backgroundColor: bgColor,
|
||||
body: SafeArea(
|
||||
child: PageView.builder(
|
||||
physics: const BouncingScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics(),
|
||||
),
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: memoryPageController,
|
||||
onPageChanged: (pageNumber) {
|
||||
@@ -252,12 +227,7 @@ class MemoryPage extends HookConsumerWidget {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 24.0,
|
||||
right: 24.0,
|
||||
top: 8.0,
|
||||
bottom: 2.0,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 24.0, right: 24.0, top: 8.0, bottom: 2.0),
|
||||
child: AnimatedBuilder(
|
||||
animation: assetController,
|
||||
builder: (context, child) {
|
||||
@@ -277,9 +247,7 @@ class MemoryPage extends HookConsumerWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
PageView.builder(
|
||||
physics: const BouncingScrollPhysics(
|
||||
parent: AlwaysScrollableScrollPhysics(),
|
||||
),
|
||||
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
|
||||
controller: assetController,
|
||||
onPageChanged: onAssetChanged,
|
||||
scrollDirection: Axis.horizontal,
|
||||
@@ -290,11 +258,7 @@ class MemoryPage extends HookConsumerWidget {
|
||||
children: [
|
||||
Container(
|
||||
color: Colors.black,
|
||||
child: MemoryCard(
|
||||
asset: asset,
|
||||
title: memories[mIndex].title,
|
||||
showTitle: index == 0,
|
||||
),
|
||||
child: MemoryCard(asset: asset, title: memories[mIndex].title, showTitle: index == 0),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Row(
|
||||
@@ -335,27 +299,19 @@ class MemoryPage extends HookConsumerWidget {
|
||||
// turn off full screen mode here
|
||||
// https://github.com/Milad-Akarie/auto_route_library/issues/1799
|
||||
context.maybePop();
|
||||
SystemChrome.setEnabledSystemUIMode(
|
||||
SystemUiMode.edgeToEdge,
|
||||
);
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
},
|
||||
shape: const CircleBorder(),
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
elevation: 0,
|
||||
child: const Icon(
|
||||
Icons.close_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: const Icon(Icons.close_rounded, color: Colors.white),
|
||||
),
|
||||
),
|
||||
if (currentAsset.value != null && currentAsset.value!.isVideo)
|
||||
Positioned(
|
||||
bottom: 24,
|
||||
right: 32,
|
||||
child: Icon(
|
||||
Icons.videocam_outlined,
|
||||
color: Colors.grey[200],
|
||||
),
|
||||
child: Icon(Icons.videocam_outlined, color: Colors.grey[200]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -29,17 +29,14 @@ class PhotosPage extends HookConsumerWidget {
|
||||
final tipOneOpacity = useState(0.0);
|
||||
final refreshCount = useState(0);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
ref.read(websocketProvider.notifier).connect();
|
||||
Future(() => ref.read(assetProvider.notifier).getAllAsset());
|
||||
Future(() => ref.read(albumProvider.notifier).refreshRemoteAlbums());
|
||||
ref.read(serverInfoProvider.notifier).getServerInfo();
|
||||
useEffect(() {
|
||||
ref.read(websocketProvider.notifier).connect();
|
||||
Future(() => ref.read(assetProvider.notifier).getAllAsset());
|
||||
Future(() => ref.read(albumProvider.notifier).refreshRemoteAlbums());
|
||||
ref.read(serverInfoProvider.notifier).getServerInfo();
|
||||
|
||||
return;
|
||||
},
|
||||
[],
|
||||
);
|
||||
return;
|
||||
}, []);
|
||||
|
||||
Widget buildLoadingIndicator() {
|
||||
Timer(const Duration(seconds: 2), () => tipOneOpacity.value = 1);
|
||||
@@ -53,9 +50,7 @@ class PhotosPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Text(
|
||||
'home_page_building_timeline',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.titleMedium?.copyWith(color: context.primaryColor),
|
||||
).tr(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -17,16 +17,9 @@ class AllMotionPhotosPage extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('search_page_motion_photos').tr(),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: motionPhotos.widgetWhen(
|
||||
onData: (assets) => ImmichAssetGrid(
|
||||
assets: assets,
|
||||
),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
),
|
||||
body: motionPhotos.widgetWhen(onData: (assets) => ImmichAssetGrid(assets: assets)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,8 @@ class AllPeoplePage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'people',
|
||||
).tr(),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
title: const Text('people').tr(),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
),
|
||||
body: curatedPeople.widgetWhen(
|
||||
onData: (people) => ExploreGrid(
|
||||
|
||||
@@ -17,19 +17,10 @@ class AllPlacesPage extends HookConsumerWidget {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'places',
|
||||
).tr(),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: places.widgetWhen(
|
||||
onData: (data) => ExploreGrid(
|
||||
curatedContent: data,
|
||||
),
|
||||
title: const Text('places').tr(),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
),
|
||||
body: places.widgetWhen(onData: (data) => ExploreGrid(curatedContent: data)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,7 @@ class AllVideosPage extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('videos').tr(),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
),
|
||||
body: MultiselectGrid(renderListProvider: allVideosTimelineProvider),
|
||||
);
|
||||
|
||||
@@ -62,17 +62,11 @@ class MapPage extends HookConsumerWidget {
|
||||
|
||||
final bounds = await mapController.value!.getVisibleRegion();
|
||||
final inBounds = markers.value
|
||||
.where(
|
||||
(m) => bounds.contains(LatLng(m.latLng.latitude, m.latLng.longitude)),
|
||||
)
|
||||
.where((m) => bounds.contains(LatLng(m.latLng.latitude, m.latLng.longitude)))
|
||||
.toList();
|
||||
// Notify bottom sheet to update asset grid only when there are new assets
|
||||
if (markersInBounds.value.length != inBounds.length) {
|
||||
bottomSheetStreamController.add(
|
||||
MapAssetsInBoundsUpdated(
|
||||
inBounds.map((e) => e.assetRemoteId).toList(),
|
||||
),
|
||||
);
|
||||
bottomSheetStreamController.add(MapAssetsInBoundsUpdated(inBounds.map((e) => e.assetRemoteId).toList()));
|
||||
}
|
||||
markersInBounds.value = inBounds;
|
||||
}
|
||||
@@ -80,9 +74,7 @@ class MapPage extends HookConsumerWidget {
|
||||
// removes all sources and layers and re-adds them with the updated markers
|
||||
Future<void> reloadLayers() async {
|
||||
if (mapController.value != null) {
|
||||
layerDebouncer.run(
|
||||
() => mapController.value!.reloadAllLayersForMarkers(markers.value),
|
||||
);
|
||||
layerDebouncer.run(() => mapController.value!.reloadAllLayersForMarkers(markers.value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,15 +89,12 @@ class MapPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
final currentAssetLink = ref.read(currentAssetProvider.notifier).ref.keepAlive();
|
||||
useEffect(() {
|
||||
final currentAssetLink = ref.read(currentAssetProvider.notifier).ref.keepAlive();
|
||||
|
||||
loadMarkers();
|
||||
return currentAssetLink.close;
|
||||
},
|
||||
[],
|
||||
);
|
||||
loadMarkers();
|
||||
return currentAssetLink.close;
|
||||
}, []);
|
||||
|
||||
// Refetch markers when map state is changed
|
||||
ref.listen(mapStateNotifierProvider, (_, current) {
|
||||
@@ -121,16 +110,9 @@ class MapPage extends HookConsumerWidget {
|
||||
});
|
||||
|
||||
// updates the selected markers position based on the current map camera
|
||||
Future<void> updateAssetMarkerPosition(
|
||||
MapMarker marker, {
|
||||
bool shouldAnimate = true,
|
||||
}) async {
|
||||
Future<void> updateAssetMarkerPosition(MapMarker marker, {bool shouldAnimate = true}) async {
|
||||
final assetPoint = await mapController.value!.toScreenLocation(marker.latLng);
|
||||
selectedMarker.value = _AssetMarkerMeta(
|
||||
point: assetPoint,
|
||||
marker: marker,
|
||||
shouldAnimate: shouldAnimate,
|
||||
);
|
||||
selectedMarker.value = _AssetMarkerMeta(point: assetPoint, marker: marker, shouldAnimate: shouldAnimate);
|
||||
(assetPoint, marker, shouldAnimate);
|
||||
}
|
||||
|
||||
@@ -160,10 +142,7 @@ class MapPage extends HookConsumerWidget {
|
||||
mapController.value = controller;
|
||||
controller.addListener(() {
|
||||
if (controller.isCameraMoving && selectedMarker.value != null) {
|
||||
updateAssetMarkerPosition(
|
||||
selectedMarker.value!.marker,
|
||||
shouldAnimate: false,
|
||||
);
|
||||
updateAssetMarkerPosition(selectedMarker.value!.marker, shouldAnimate: false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,22 +159,13 @@ class MapPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
// Since we only have a single asset, we can just show GroupAssetBy.none
|
||||
final renderList = await RenderList.fromAssets(
|
||||
[asset],
|
||||
GroupAssetsBy.none,
|
||||
);
|
||||
final renderList = await RenderList.fromAssets([asset], GroupAssetsBy.none);
|
||||
|
||||
ref.read(currentAssetProvider.notifier).set(asset);
|
||||
if (asset.isVideo) {
|
||||
ref.read(showControlsProvider.notifier).show = false;
|
||||
}
|
||||
context.pushRoute(
|
||||
GalleryViewerRoute(
|
||||
initialIndex: 0,
|
||||
heroOffset: 0,
|
||||
renderList: renderList,
|
||||
),
|
||||
);
|
||||
context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList));
|
||||
}
|
||||
|
||||
/// BOTTOM SHEET CALLBACKS
|
||||
@@ -216,10 +186,7 @@ class MapPage extends HookConsumerWidget {
|
||||
if (mapController.value != null && assetMarker != null) {
|
||||
// Offset the latitude a little to show the marker just above the viewports center
|
||||
final offset = context.isMobile ? 0.02 : 0;
|
||||
final latlng = LatLng(
|
||||
assetMarker.latLng.latitude - offset,
|
||||
assetMarker.latLng.longitude,
|
||||
);
|
||||
final latlng = LatLng(assetMarker.latLng.latitude - offset, assetMarker.latLng.longitude);
|
||||
mapController.value!.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(latlng, mapZoomToAssetLevel),
|
||||
duration: const Duration(milliseconds: 800),
|
||||
@@ -243,10 +210,7 @@ class MapPage extends HookConsumerWidget {
|
||||
|
||||
if (mapController.value != null && location != null) {
|
||||
mapController.value!.animateCamera(
|
||||
CameraUpdate.newLatLngZoom(
|
||||
LatLng(location.latitude, location.longitude),
|
||||
mapZoomToAssetLevel,
|
||||
),
|
||||
CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), mapZoomToAssetLevel),
|
||||
duration: const Duration(milliseconds: 800),
|
||||
);
|
||||
}
|
||||
@@ -311,9 +275,7 @@ class MapPage extends HookConsumerWidget {
|
||||
bottom: context.padding.bottom + 16,
|
||||
child: ElevatedButton(
|
||||
onPressed: onZoomToLocation,
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.my_location),
|
||||
),
|
||||
),
|
||||
@@ -344,11 +306,7 @@ class _AssetMarkerMeta {
|
||||
final MapMarker marker;
|
||||
final bool shouldAnimate;
|
||||
|
||||
const _AssetMarkerMeta({
|
||||
required this.point,
|
||||
required this.marker,
|
||||
required this.shouldAnimate,
|
||||
});
|
||||
const _AssetMarkerMeta({required this.point, required this.marker, required this.shouldAnimate});
|
||||
|
||||
@override
|
||||
String toString() => '_AssetMarkerMeta(point: $point, marker: $marker, shouldAnimate: $shouldAnimate)';
|
||||
|
||||
@@ -16,10 +16,7 @@ import 'package:immich_mobile/utils/map_utils.dart';
|
||||
class MapLocationPickerPage extends HookConsumerWidget {
|
||||
final LatLng initialLatLng;
|
||||
|
||||
const MapLocationPickerPage({
|
||||
super.key,
|
||||
this.initialLatLng = const LatLng(0, 0),
|
||||
});
|
||||
const MapLocationPickerPage({super.key, this.initialLatLng = const LatLng(0, 0)});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -66,10 +63,7 @@ class MapLocationPickerPage extends HookConsumerWidget {
|
||||
onData: (style) => Container(
|
||||
clipBehavior: Clip.antiAliasWithSaveLayer,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(40),
|
||||
bottomRight: Radius.circular(40),
|
||||
),
|
||||
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(40), bottomRight: Radius.circular(40)),
|
||||
),
|
||||
child: MapLibreMap(
|
||||
initialCameraPosition: CameraPosition(target: initialLatLng, zoom: 12),
|
||||
@@ -108,9 +102,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ElevatedButton(
|
||||
onPressed: onClose,
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(shape: const CircleBorder()),
|
||||
child: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
),
|
||||
),
|
||||
@@ -126,11 +118,7 @@ class _BottomBar extends StatelessWidget {
|
||||
final Function() onUseLocation;
|
||||
final Function() onGetCurrentLocation;
|
||||
|
||||
const _BottomBar({
|
||||
required this.selectedLatLng,
|
||||
required this.onUseLocation,
|
||||
required this.onGetCurrentLocation,
|
||||
});
|
||||
const _BottomBar({required this.selectedLatLng, required this.onUseLocation, required this.onGetCurrentLocation});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -149,9 +137,8 @@ class _BottomBar extends StatelessWidget {
|
||||
const SizedBox(width: 15),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: selectedLatLng,
|
||||
builder: (_, value, __) => Text(
|
||||
"${value.latitude.toStringAsFixed(4)}, ${value.longitude.toStringAsFixed(4)}",
|
||||
),
|
||||
builder: (_, value, __) =>
|
||||
Text("${value.latitude.toStringAsFixed(4)}, ${value.longitude.toStringAsFixed(4)}"),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -162,10 +149,7 @@ class _BottomBar extends StatelessWidget {
|
||||
onPressed: onUseLocation,
|
||||
child: const Text("map_location_picker_page_use_location").tr(),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: onGetCurrentLocation,
|
||||
child: const Icon(Icons.my_location),
|
||||
),
|
||||
ElevatedButton(onPressed: onGetCurrentLocation, child: const Icon(Icons.my_location)),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -15,11 +15,7 @@ class PersonResultPage extends HookConsumerWidget {
|
||||
final String personId;
|
||||
final String personName;
|
||||
|
||||
const PersonResultPage({
|
||||
super.key,
|
||||
required this.personId,
|
||||
required this.personName,
|
||||
});
|
||||
const PersonResultPage({super.key, required this.personId, required this.personName});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -30,10 +26,7 @@ class PersonResultPage extends HookConsumerWidget {
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (BuildContext context) {
|
||||
return PersonNameEditForm(
|
||||
personId: personId,
|
||||
personName: name.value,
|
||||
);
|
||||
return PersonNameEditForm(personId: personId, personName: name.value);
|
||||
},
|
||||
).then((result) {
|
||||
if (result != null && result.success) {
|
||||
@@ -55,10 +48,7 @@ class PersonResultPage extends HookConsumerWidget {
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.edit_outlined),
|
||||
title: const Text(
|
||||
'edit_name',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
title: const Text('edit_name', style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
onTap: showEditNameDialog,
|
||||
),
|
||||
],
|
||||
@@ -75,27 +65,13 @@ class PersonResultPage extends HookConsumerWidget {
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'add_a_name',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
).tr(),
|
||||
Text(
|
||||
'find_them_fast',
|
||||
style: context.textTheme.labelLarge,
|
||||
).tr(),
|
||||
Text('add_a_name', style: context.textTheme.titleMedium?.copyWith(color: context.primaryColor)).tr(),
|
||||
Text('find_them_fast', style: context.textTheme.labelLarge).tr(),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
name.value,
|
||||
style: context.textTheme.titleLarge,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
children: [Text(name.value, style: context.textTheme.titleLarge, overflow: TextOverflow.ellipsis)],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -103,16 +79,8 @@ class PersonResultPage extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(name.value),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: buildBottomSheet,
|
||||
icon: const Icon(Icons.more_vert_rounded),
|
||||
),
|
||||
],
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
actions: [IconButton(onPressed: buildBottomSheet, icon: const Icon(Icons.more_vert_rounded))],
|
||||
),
|
||||
body: MultiselectGrid(
|
||||
renderListProvider: personAssetsProvider(personId),
|
||||
@@ -122,16 +90,10 @@ class PersonResultPage extends HookConsumerWidget {
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 36,
|
||||
backgroundImage: NetworkImage(
|
||||
getFaceThumbnailUrl(personId),
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
),
|
||||
backgroundImage: NetworkImage(getFaceThumbnailUrl(personId), headers: ApiService.getRequestHeaders()),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
|
||||
child: buildTitleBlock(),
|
||||
),
|
||||
child: Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: buildTitleBlock()),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -17,16 +17,9 @@ class RecentlyTakenPage extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('recently_taken_page_title').tr(),
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: recents.widgetWhen(
|
||||
onData: (searchResponse) => ImmichAssetGrid(
|
||||
assets: searchResponse,
|
||||
),
|
||||
leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)),
|
||||
),
|
||||
body: recents.widgetWhen(onData: (searchResponse) => ImmichAssetGrid(assets: searchResponse)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,12 +41,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
location: prefilter?.location ?? SearchLocationFilter(),
|
||||
camera: prefilter?.camera ?? SearchCameraFilter(),
|
||||
date: prefilter?.date ?? SearchDateFilter(),
|
||||
display: prefilter?.display ??
|
||||
SearchDisplayFilters(
|
||||
isNotInAlbum: false,
|
||||
isArchive: false,
|
||||
isFavorite: false,
|
||||
),
|
||||
display: prefilter?.display ?? SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false),
|
||||
mediaType: prefilter?.mediaType ?? AssetType.other,
|
||||
language: "${context.locale.languageCode}-${context.locale.countryCode}",
|
||||
),
|
||||
@@ -65,10 +60,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
|
||||
SnackBar searchInfoSnackBar(String message) {
|
||||
return SnackBar(
|
||||
content: Text(
|
||||
message,
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
content: Text(message, style: context.textTheme.labelLarge),
|
||||
showCloseIcon: true,
|
||||
behavior: SnackBarBehavior.fixed,
|
||||
closeIconColor: context.colorScheme.onSurface,
|
||||
@@ -89,9 +81,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||
|
||||
if (!hasResult) {
|
||||
context.showSnackBar(
|
||||
searchInfoSnackBar('search_no_result'.tr()),
|
||||
);
|
||||
context.showSnackBar(searchInfoSnackBar('search_no_result'.tr()));
|
||||
}
|
||||
|
||||
previousFilter.value = filter.value;
|
||||
@@ -103,9 +93,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||
|
||||
if (!hasResult) {
|
||||
context.showSnackBar(
|
||||
searchInfoSnackBar('search_no_more_result'.tr()),
|
||||
);
|
||||
context.showSnackBar(searchInfoSnackBar('search_no_more_result'.tr()));
|
||||
}
|
||||
|
||||
isSearching.value = false;
|
||||
@@ -113,39 +101,26 @@ class SearchPage extends HookConsumerWidget {
|
||||
|
||||
searchPrefilter() {
|
||||
if (prefilter != null) {
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() {
|
||||
search();
|
||||
Future.delayed(Duration.zero, () {
|
||||
search();
|
||||
|
||||
if (prefilter!.location.city != null) {
|
||||
locationCurrentFilterWidget.value = Text(
|
||||
prefilter!.location.city!,
|
||||
style: context.textTheme.labelLarge,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (prefilter!.location.city != null) {
|
||||
locationCurrentFilterWidget.value = Text(prefilter!.location.city!, style: context.textTheme.labelLarge);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
Future.microtask(
|
||||
() => ref.invalidate(paginatedSearchProvider),
|
||||
);
|
||||
searchPrefilter();
|
||||
useEffect(() {
|
||||
Future.microtask(() => ref.invalidate(paginatedSearchProvider));
|
||||
searchPrefilter();
|
||||
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
showPeoplePicker() {
|
||||
handleOnSelect(Set<PersonDto> value) {
|
||||
filter.value = filter.value.copyWith(
|
||||
people: value,
|
||||
);
|
||||
filter.value = filter.value.copyWith(people: value);
|
||||
|
||||
peopleCurrentFilterWidget.value = Text(
|
||||
value.map((e) => e.name != '' ? e.name : 'no_name'.tr()).join(', '),
|
||||
@@ -154,9 +129,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
handleClear() {
|
||||
filter.value = filter.value.copyWith(
|
||||
people: {},
|
||||
);
|
||||
filter.value = filter.value.copyWith(people: {});
|
||||
|
||||
peopleCurrentFilterWidget.value = null;
|
||||
search();
|
||||
@@ -172,10 +145,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
expanded: true,
|
||||
onSearch: search,
|
||||
onClear: handleClear,
|
||||
child: PeoplePicker(
|
||||
onSelect: handleOnSelect,
|
||||
filter: filter.value.people,
|
||||
),
|
||||
child: PeoplePicker(onSelect: handleOnSelect, filter: filter.value.people),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -184,11 +154,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
showLocationPicker() {
|
||||
handleOnSelect(Map<String, String?> value) {
|
||||
filter.value = filter.value.copyWith(
|
||||
location: SearchLocationFilter(
|
||||
country: value['country'],
|
||||
city: value['city'],
|
||||
state: value['state'],
|
||||
),
|
||||
location: SearchLocationFilter(country: value['country'], city: value['city'], state: value['state']),
|
||||
);
|
||||
|
||||
final locationText = <String>[];
|
||||
@@ -204,16 +170,11 @@ class SearchPage extends HookConsumerWidget {
|
||||
locationText.add(value['city']!);
|
||||
}
|
||||
|
||||
locationCurrentFilterWidget.value = Text(
|
||||
locationText.join(', '),
|
||||
style: context.textTheme.labelLarge,
|
||||
);
|
||||
locationCurrentFilterWidget.value = Text(locationText.join(', '), style: context.textTheme.labelLarge);
|
||||
}
|
||||
|
||||
handleClear() {
|
||||
filter.value = filter.value.copyWith(
|
||||
location: SearchLocationFilter(),
|
||||
);
|
||||
filter.value = filter.value.copyWith(location: SearchLocationFilter());
|
||||
|
||||
locationCurrentFilterWidget.value = null;
|
||||
search();
|
||||
@@ -230,15 +191,10 @@ class SearchPage extends HookConsumerWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: context.viewInsets.bottom,
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: context.viewInsets.bottom),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: LocationPicker(
|
||||
onSelected: handleOnSelect,
|
||||
filter: filter.value.location,
|
||||
),
|
||||
child: LocationPicker(onSelected: handleOnSelect, filter: filter.value.location),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -249,10 +205,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
showCameraPicker() {
|
||||
handleOnSelect(Map<String, String?> value) {
|
||||
filter.value = filter.value.copyWith(
|
||||
camera: SearchCameraFilter(
|
||||
make: value['make'],
|
||||
model: value['model'],
|
||||
),
|
||||
camera: SearchCameraFilter(make: value['make'], model: value['model']),
|
||||
);
|
||||
|
||||
cameraCurrentFilterWidget.value = Text(
|
||||
@@ -262,9 +215,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
handleClear() {
|
||||
filter.value = filter.value.copyWith(
|
||||
camera: SearchCameraFilter(),
|
||||
);
|
||||
filter.value = filter.value.copyWith(camera: SearchCameraFilter());
|
||||
|
||||
cameraCurrentFilterWidget.value = null;
|
||||
search();
|
||||
@@ -280,10 +231,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
onClear: handleClear,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: CameraPicker(
|
||||
onSelect: handleOnSelect,
|
||||
filter: filter.value.camera,
|
||||
),
|
||||
child: CameraPicker(onSelect: handleOnSelect, filter: filter.value.camera),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -315,9 +263,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (date == null) {
|
||||
filter.value = filter.value.copyWith(
|
||||
date: SearchDateFilter(),
|
||||
);
|
||||
filter.value = filter.value.copyWith(date: SearchDateFilter());
|
||||
|
||||
dateRangeCurrentFilterWidget.value = null;
|
||||
search();
|
||||
@@ -327,13 +273,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
filter.value = filter.value.copyWith(
|
||||
date: SearchDateFilter(
|
||||
takenAfter: date.start,
|
||||
takenBefore: date.end.add(
|
||||
const Duration(
|
||||
hours: 23,
|
||||
minutes: 59,
|
||||
seconds: 59,
|
||||
),
|
||||
),
|
||||
takenBefore: date.end.add(const Duration(hours: 23, minutes: 59, seconds: 59)),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -361,24 +301,20 @@ class SearchPage extends HookConsumerWidget {
|
||||
// MEDIA PICKER
|
||||
showMediaTypePicker() {
|
||||
handleOnSelected(AssetType assetType) {
|
||||
filter.value = filter.value.copyWith(
|
||||
mediaType: assetType,
|
||||
);
|
||||
filter.value = filter.value.copyWith(mediaType: assetType);
|
||||
|
||||
mediaTypeCurrentFilterWidget.value = Text(
|
||||
assetType == AssetType.image
|
||||
? 'image'.tr()
|
||||
: assetType == AssetType.video
|
||||
? 'video'.tr()
|
||||
: 'all'.tr(),
|
||||
? 'video'.tr()
|
||||
: 'all'.tr(),
|
||||
style: context.textTheme.labelLarge,
|
||||
);
|
||||
}
|
||||
|
||||
handleClear() {
|
||||
filter.value = filter.value.copyWith(
|
||||
mediaType: AssetType.other,
|
||||
);
|
||||
filter.value = filter.value.copyWith(mediaType: AssetType.other);
|
||||
|
||||
mediaTypeCurrentFilterWidget.value = null;
|
||||
search();
|
||||
@@ -390,10 +326,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
title: 'search_filter_media_type_title'.tr(),
|
||||
onSearch: search,
|
||||
onClear: handleClear,
|
||||
child: MediaTypePicker(
|
||||
onSelect: handleOnSelected,
|
||||
filter: filter.value.mediaType,
|
||||
),
|
||||
child: MediaTypePicker(onSelect: handleOnSelected, filter: filter.value.mediaType),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -405,31 +338,19 @@ class SearchPage extends HookConsumerWidget {
|
||||
value.forEach((key, value) {
|
||||
switch (key) {
|
||||
case DisplayOption.notInAlbum:
|
||||
filter.value = filter.value.copyWith(
|
||||
display: filter.value.display.copyWith(
|
||||
isNotInAlbum: value,
|
||||
),
|
||||
);
|
||||
filter.value = filter.value.copyWith(display: filter.value.display.copyWith(isNotInAlbum: value));
|
||||
if (value) {
|
||||
filterText.add('search_filter_display_option_not_in_album'.tr());
|
||||
}
|
||||
break;
|
||||
case DisplayOption.archive:
|
||||
filter.value = filter.value.copyWith(
|
||||
display: filter.value.display.copyWith(
|
||||
isArchive: value,
|
||||
),
|
||||
);
|
||||
filter.value = filter.value.copyWith(display: filter.value.display.copyWith(isArchive: value));
|
||||
if (value) {
|
||||
filterText.add('archive'.tr());
|
||||
}
|
||||
break;
|
||||
case DisplayOption.favorite:
|
||||
filter.value = filter.value.copyWith(
|
||||
display: filter.value.display.copyWith(
|
||||
isFavorite: value,
|
||||
),
|
||||
);
|
||||
filter.value = filter.value.copyWith(display: filter.value.display.copyWith(isFavorite: value));
|
||||
if (value) {
|
||||
filterText.add('favorite'.tr());
|
||||
}
|
||||
@@ -442,19 +363,12 @@ class SearchPage extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
displayOptionCurrentFilterWidget.value = Text(
|
||||
filterText.join(', '),
|
||||
style: context.textTheme.labelLarge,
|
||||
);
|
||||
displayOptionCurrentFilterWidget.value = Text(filterText.join(', '), style: context.textTheme.labelLarge);
|
||||
}
|
||||
|
||||
handleClear() {
|
||||
filter.value = filter.value.copyWith(
|
||||
display: SearchDisplayFilters(
|
||||
isNotInAlbum: false,
|
||||
isArchive: false,
|
||||
isFavorite: false,
|
||||
),
|
||||
display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false),
|
||||
);
|
||||
|
||||
displayOptionCurrentFilterWidget.value = null;
|
||||
@@ -467,10 +381,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
title: 'display_options'.tr(),
|
||||
onSearch: search,
|
||||
onClear: handleClear,
|
||||
child: DisplayOptionPicker(
|
||||
onSelect: handleOnSelect,
|
||||
filter: filter.value.display,
|
||||
),
|
||||
child: DisplayOptionPicker(onSelect: handleOnSelect, filter: filter.value.display),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -478,27 +389,15 @@ class SearchPage extends HookConsumerWidget {
|
||||
handleTextSubmitted(String value) {
|
||||
switch (textSearchType.value) {
|
||||
case TextSearchType.context:
|
||||
filter.value = filter.value.copyWith(
|
||||
filename: '',
|
||||
context: value,
|
||||
description: '',
|
||||
);
|
||||
filter.value = filter.value.copyWith(filename: '', context: value, description: '');
|
||||
|
||||
break;
|
||||
case TextSearchType.filename:
|
||||
filter.value = filter.value.copyWith(
|
||||
filename: value,
|
||||
context: '',
|
||||
description: '',
|
||||
);
|
||||
filter.value = filter.value.copyWith(filename: value, context: '', description: '');
|
||||
|
||||
break;
|
||||
case TextSearchType.description:
|
||||
filter.value = filter.value.copyWith(
|
||||
filename: '',
|
||||
context: '',
|
||||
description: value,
|
||||
);
|
||||
filter.value = filter.value.copyWith(filename: '', context: '', description: value);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -506,10 +405,10 @@ class SearchPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
IconData getSearchPrefixIcon() => switch (textSearchType.value) {
|
||||
TextSearchType.context => Icons.image_search_rounded,
|
||||
TextSearchType.filename => Icons.abc_rounded,
|
||||
TextSearchType.description => Icons.text_snippet_outlined,
|
||||
};
|
||||
TextSearchType.context => Icons.image_search_rounded,
|
||||
TextSearchType.filename => Icons.abc_rounded,
|
||||
TextSearchType.description => Icons.text_snippet_outlined,
|
||||
};
|
||||
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
@@ -522,21 +421,11 @@ class SearchPage extends HookConsumerWidget {
|
||||
style: MenuStyle(
|
||||
elevation: const WidgetStatePropertyAll(1),
|
||||
shape: WidgetStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(
|
||||
EdgeInsets.all(4),
|
||||
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.all(4)),
|
||||
),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
MenuController controller,
|
||||
Widget? child,
|
||||
) {
|
||||
builder: (BuildContext context, MenuController controller, Widget? child) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
@@ -610,13 +499,8 @@ class SearchPage extends HookConsumerWidget {
|
||||
],
|
||||
title: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: context.colorScheme.onSurface.withAlpha(0),
|
||||
width: 0,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
border: Border.all(color: context.colorScheme.onSurface.withAlpha(0), width: 0),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withValues(alpha: 0.075),
|
||||
@@ -632,12 +516,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
key: const Key('search_text_field'),
|
||||
controller: textSearchController,
|
||||
contentPadding: prefilter != null ? const EdgeInsets.only(left: 24) : const EdgeInsets.all(8),
|
||||
prefixIcon: prefilter != null
|
||||
? null
|
||||
: Icon(
|
||||
getSearchPrefixIcon(),
|
||||
color: context.colorScheme.primary,
|
||||
),
|
||||
prefixIcon: prefilter != null ? null : Icon(getSearchPrefixIcon(), color: context.colorScheme.primary),
|
||||
onSubmitted: handleTextSubmitted,
|
||||
focusNode: ref.watch(searchInputFocusProvider),
|
||||
),
|
||||
@@ -697,14 +576,9 @@ class SearchPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
if (isSearching.value)
|
||||
const Expanded(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
const Expanded(child: Center(child: CircularProgressIndicator()))
|
||||
else
|
||||
SearchResultGrid(
|
||||
onScrollEnd: loadMoreSearchResult,
|
||||
isSearching: isSearching.value,
|
||||
),
|
||||
SearchResultGrid(onScrollEnd: loadMoreSearchResult, isSearching: isSearching.value),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -715,11 +589,7 @@ class SearchResultGrid extends StatelessWidget {
|
||||
final VoidCallback onScrollEnd;
|
||||
final bool isSearching;
|
||||
|
||||
const SearchResultGrid({
|
||||
super.key,
|
||||
required this.onScrollEnd,
|
||||
this.isSearching = false,
|
||||
});
|
||||
const SearchResultGrid({super.key, required this.onScrollEnd, this.isSearching = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -777,12 +647,7 @@ class SearchEmptyContent extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Center(
|
||||
child: Text(
|
||||
'search_page_search_photos_videos'.tr(),
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
Center(child: Text('search_page_search_photos_videos'.tr(), style: context.textTheme.labelLarge)),
|
||||
const SizedBox(height: 32),
|
||||
const QuickLinkList(),
|
||||
],
|
||||
@@ -798,13 +663,8 @@ class QuickLinkList extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
border: Border.all(
|
||||
color: context.colorScheme.outline.withAlpha(10),
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
border: Border.all(color: context.colorScheme.outline.withAlpha(10), width: 1),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
context.colorScheme.primary.withAlpha(10),
|
||||
@@ -868,19 +728,9 @@ class QuickLink extends StatelessWidget {
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
leading: Icon(
|
||||
icon,
|
||||
size: 26,
|
||||
),
|
||||
title: Text(
|
||||
title,
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: borderRadius),
|
||||
leading: Icon(icon, size: 26),
|
||||
title: Text(title, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500)),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ import 'package:immich_mobile/widgets/settings/beta_sync_settings/beta_sync_sett
|
||||
|
||||
@RoutePage()
|
||||
class BetaSyncSettingsPage extends StatelessWidget {
|
||||
const BetaSyncSettingsPage({
|
||||
super.key,
|
||||
});
|
||||
const BetaSyncSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -18,9 +16,7 @@ class BetaSyncSettingsPage extends StatelessWidget {
|
||||
leading: IconButton(
|
||||
onPressed: () => context.maybePop(true),
|
||||
splashRadius: 24,
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_rounded,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||
),
|
||||
),
|
||||
body: const BetaSyncSettings(),
|
||||
|
||||
@@ -60,22 +60,16 @@ class ShareIntentPage extends HookConsumerWidget {
|
||||
appBar: AppBar(
|
||||
title: Column(
|
||||
children: [
|
||||
const Text('upload_to_immich').tr(
|
||||
namedArgs: {'count': candidates.length.toString()},
|
||||
),
|
||||
const Text('upload_to_immich').tr(namedArgs: {'count': candidates.length.toString()}),
|
||||
Text(
|
||||
currentEndpoint,
|
||||
style: context.textTheme.labelMedium?.copyWith(
|
||||
color: context.colorScheme.onSurface.withAlpha(200),
|
||||
),
|
||||
style: context.textTheme.labelMedium?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)),
|
||||
),
|
||||
],
|
||||
),
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
context.navigateTo(
|
||||
Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute(),
|
||||
);
|
||||
context.navigateTo(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute());
|
||||
},
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
@@ -84,16 +78,10 @@ class ShareIntentPage extends HookConsumerWidget {
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, index) {
|
||||
final attachment = attachments[index];
|
||||
final target = candidates.firstWhere(
|
||||
(element) => element.id == attachment.id,
|
||||
orElse: () => attachment,
|
||||
);
|
||||
final target = candidates.firstWhere((element) => element.id == attachment.id, orElse: () => attachment);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 16,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16),
|
||||
child: LargeLeadingTile(
|
||||
onTap: () => toggleSelection(attachment),
|
||||
disabled: isUploaded.value,
|
||||
@@ -103,21 +91,11 @@ class ShareIntentPage extends HookConsumerWidget {
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: attachment.isImage
|
||||
? Image.file(
|
||||
attachment.file,
|
||||
width: 64,
|
||||
height: 64,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
? Image.file(attachment.file, width: 64, height: 64, fit: BoxFit.cover)
|
||||
: const SizedBox(
|
||||
width: 64,
|
||||
height: 64,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.videocam,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
child: Center(child: Icon(Icons.videocam, color: Colors.white)),
|
||||
),
|
||||
),
|
||||
if (attachment.isImage)
|
||||
@@ -128,25 +106,13 @@ class ShareIntentPage extends HookConsumerWidget {
|
||||
Icons.image,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(0, 0),
|
||||
blurRadius: 8.0,
|
||||
color: Colors.black45,
|
||||
),
|
||||
],
|
||||
shadows: [Shadow(offset: Offset(0, 0), blurRadius: 8.0, color: Colors.black45)],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Text(
|
||||
attachment.fileName,
|
||||
style: context.textTheme.titleSmall,
|
||||
),
|
||||
subtitle: Text(
|
||||
attachment.fileSize,
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
title: Text(attachment.fileName, style: context.textTheme.titleSmall),
|
||||
subtitle: Text(attachment.fileSize, style: context.textTheme.labelLarge),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: UploadStatusIcon(
|
||||
@@ -185,22 +151,14 @@ class UploadingText extends StatelessWidget {
|
||||
return element.status == UploadStatus.complete;
|
||||
}).length;
|
||||
|
||||
return const Text("shared_intent_upload_button_progress_text").tr(
|
||||
namedArgs: {
|
||||
'current': uploadedCount.toString(),
|
||||
'total': candidates.length.toString(),
|
||||
},
|
||||
);
|
||||
return const Text(
|
||||
"shared_intent_upload_button_progress_text",
|
||||
).tr(namedArgs: {'current': uploadedCount.toString(), 'total': candidates.length.toString()});
|
||||
}
|
||||
}
|
||||
|
||||
class UploadStatusIcon extends StatelessWidget {
|
||||
const UploadStatusIcon({
|
||||
super.key,
|
||||
required this.status,
|
||||
required this.selected,
|
||||
this.progress = 0,
|
||||
});
|
||||
const UploadStatusIcon({super.key, required this.status, required this.selected, this.progress = 0});
|
||||
|
||||
final UploadStatus status;
|
||||
final double progress;
|
||||
@@ -218,55 +176,42 @@ class UploadStatusIcon extends StatelessWidget {
|
||||
|
||||
final statusIcon = switch (status) {
|
||||
UploadStatus.enqueued => Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: 'enqueued'.tr(),
|
||||
),
|
||||
Icons.check_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: 'enqueued'.tr(),
|
||||
),
|
||||
UploadStatus.running => Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween<double>(begin: 0.0, end: progress),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
builder: (context, value, _) => CircularProgressIndicator(
|
||||
backgroundColor: context.colorScheme.surfaceContainerLow,
|
||||
strokeWidth: 3,
|
||||
value: value,
|
||||
semanticsLabel: 'uploading'.tr(),
|
||||
),
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween<double>(begin: 0.0, end: progress),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
builder: (context, value, _) => CircularProgressIndicator(
|
||||
backgroundColor: context.colorScheme.surfaceContainerLow,
|
||||
strokeWidth: 3,
|
||||
value: value,
|
||||
semanticsLabel: 'uploading'.tr(),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(progress * 100).toStringAsFixed(0),
|
||||
style: context.textTheme.labelSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
UploadStatus.complete => Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.green,
|
||||
semanticLabel: 'completed'.tr(),
|
||||
),
|
||||
UploadStatus.notFound || UploadStatus.failed => Icon(
|
||||
Icons.error_rounded,
|
||||
color: Colors.red,
|
||||
semanticLabel: 'failed'.tr(),
|
||||
),
|
||||
UploadStatus.canceled => Icon(
|
||||
Icons.cancel_rounded,
|
||||
color: Colors.red,
|
||||
semanticLabel: 'canceled'.tr(),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(progress * 100).toStringAsFixed(0),
|
||||
style: context.textTheme.labelSmall?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
UploadStatus.complete => Icon(Icons.check_circle_rounded, color: Colors.green, semanticLabel: 'completed'.tr()),
|
||||
UploadStatus.notFound ||
|
||||
UploadStatus.failed => Icon(Icons.error_rounded, color: Colors.red, semanticLabel: 'failed'.tr()),
|
||||
UploadStatus.canceled => Icon(Icons.cancel_rounded, color: Colors.red, semanticLabel: 'canceled'.tr()),
|
||||
UploadStatus.waitingToRetry || UploadStatus.paused => Icon(
|
||||
Icons.pause_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: 'paused'.tr(),
|
||||
),
|
||||
Icons.pause_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: 'paused'.tr(),
|
||||
),
|
||||
};
|
||||
|
||||
return statusIcon;
|
||||
|
||||
Reference in New Issue
Block a user