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:
shenlong
2025-07-29 00:34:03 +05:30
committed by GitHub
parent 9b3718120b
commit e52b9d15b5
643 changed files with 32561 additions and 35292 deletions
@@ -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) {
+4 -5
View File
@@ -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),
);
}
}
+9 -40
View File
@@ -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",
+3 -15
View File
@@ -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)),
);
}
}
+4 -15
View File
@@ -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());
+45 -116
View File
@@ -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(),
+15 -53
View File
@@ -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(
+5 -8
View File
@@ -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,
),
);
},
+19 -51
View File
@@ -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(
+18 -50
View File
@@ -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,
+17 -41
View File
@@ -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,
),
),
),
+22 -67
View File
@@ -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(
+13 -53
View File
@@ -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;
+14 -41
View File
@@ -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 -82
View File
@@ -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),
+15 -40
View File
@@ -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),
),
),
),
+2 -7
View File
@@ -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}),
);
}
+2 -7
View File
@@ -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(),
);
}
+36 -100
View File
@@ -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) {
+26 -102
View File
@@ -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(),
),
],
+19 -53
View File
@@ -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());
}
}
+4 -6
View File
@@ -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()),
],
),
),
+14 -58
View File
@@ -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]),
),
],
),
+8 -13
View File
@@ -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)),
);
}
}
+2 -7
View File
@@ -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(
+3 -12
View File
@@ -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)),
);
}
}
+1 -4
View File
@@ -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),
);
+17 -59
View File
@@ -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)),
],
),
],
+10 -48
View File
@@ -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)),
);
}
}
+61 -211
View File
@@ -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;