chore: bump line length to 120 (#20191)
This commit is contained in:
@@ -24,8 +24,7 @@ class ActivityTextField extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final album = ref.watch(currentAlbumProvider)!;
|
||||
final asset = ref.watch(currentAssetProvider);
|
||||
final activityNotifier = ref
|
||||
.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier);
|
||||
final activityNotifier = ref.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier);
|
||||
final user = ref.watch(currentUserProvider);
|
||||
final inputController = useTextEditingController();
|
||||
final inputFocusNode = useFocusNode();
|
||||
@@ -88,9 +87,7 @@ class ActivityTextField extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
suffixIconColor: liked ? Colors.red[700] : null,
|
||||
hintText: !isEnabled
|
||||
? 'shared_album_activities_input_disable'.tr()
|
||||
: 'say_something'.tr(),
|
||||
hintText: !isEnabled ? 'shared_album_activities_input_disable'.tr() : 'say_something'.tr(),
|
||||
hintStyle: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
|
||||
@@ -38,11 +38,8 @@ class ActivityTile extends HookConsumerWidget {
|
||||
leftAlign: isLike || showAssetThumbnail,
|
||||
),
|
||||
// No subtitle for like, so center title
|
||||
titleAlignment:
|
||||
!isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
|
||||
trailing: showAssetThumbnail
|
||||
? _ActivityAssetThumbnail(activity.assetId!)
|
||||
: null,
|
||||
titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
|
||||
trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!) : null,
|
||||
subtitle: !isLike ? Text(activity.comment!) : null,
|
||||
);
|
||||
}
|
||||
@@ -62,12 +59,10 @@ class _ActivityTitle extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
|
||||
final textStyle = context.textTheme.bodyMedium
|
||||
?.copyWith(color: textColor.withValues(alpha: 0.6));
|
||||
final textStyle = context.textTheme.bodyMedium?.copyWith(color: textColor.withValues(alpha: 0.6));
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment:
|
||||
leftAlign ? MainAxisAlignment.start : MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: leftAlign ? MainAxisAlignment.start : MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
|
||||
@@ -25,13 +25,11 @@ class AddToAlbumSliverList extends HookConsumerWidget {
|
||||
final albumSortMode = ref.watch(albumSortByOptionsProvider);
|
||||
final albumSortIsReverse = ref.watch(albumSortOrderProvider);
|
||||
final sortedAlbums = albumSortMode.sortFn(albums, albumSortIsReverse);
|
||||
final sortedSharedAlbums =
|
||||
albumSortMode.sortFn(sharedAlbums, albumSortIsReverse);
|
||||
final sortedSharedAlbums = albumSortMode.sortFn(sharedAlbums, albumSortIsReverse);
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1),
|
||||
(context, index) {
|
||||
delegate:
|
||||
SliverChildBuilderDelegate(childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1), (context, index) {
|
||||
// Build shared expander
|
||||
if (index == 0 && sortedSharedAlbums.isNotEmpty) {
|
||||
return Padding(
|
||||
@@ -47,9 +45,7 @@ class AddToAlbumSliverList extends HookConsumerWidget {
|
||||
itemCount: sortedSharedAlbums.length,
|
||||
itemBuilder: (context, index) => AlbumThumbnailListTile(
|
||||
album: sortedSharedAlbums[index],
|
||||
onTap: enabled
|
||||
? () => onAddToAlbum(sortedSharedAlbums[index])
|
||||
: () {},
|
||||
onTap: enabled ? () => onAddToAlbum(sortedSharedAlbums[index]) : () {},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -109,9 +109,7 @@ class AlbumThumbnailCard extends ConsumerWidget {
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(20),
|
||||
),
|
||||
child: album.thumbnail.value == null
|
||||
? buildEmptyThumbnail()
|
||||
: buildAlbumThumbnail(),
|
||||
child: album.thumbnail.value == null ? buildEmptyThumbnail() : buildAlbumThumbnail(),
|
||||
),
|
||||
),
|
||||
if (showTitle) ...[
|
||||
|
||||
@@ -50,10 +50,8 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
||||
type: AssetMediaSize.thumbnail,
|
||||
),
|
||||
httpHeaders: ApiService.getRequestHeaders(),
|
||||
cacheKey:
|
||||
getAlbumThumbNailCacheKey(album, type: AssetMediaSize.thumbnail),
|
||||
errorWidget: (context, url, error) =>
|
||||
const Icon(Icons.image_not_supported_outlined),
|
||||
cacheKey: getAlbumThumbNailCacheKey(album, type: AssetMediaSize.thumbnail),
|
||||
errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,9 +68,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: album.thumbnail.value == null
|
||||
? buildEmptyThumbnail()
|
||||
: buildAlbumThumbnail(),
|
||||
child: album.thumbnail.value == null ? buildEmptyThumbnail() : buildAlbumThumbnail(),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
|
||||
@@ -12,8 +12,7 @@ import 'package:immich_mobile/providers/album/current_album.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class AlbumViewerAppbar extends HookConsumerWidget
|
||||
implements PreferredSizeWidget {
|
||||
class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidget {
|
||||
const AlbumViewerAppbar({
|
||||
super.key,
|
||||
required this.userId,
|
||||
@@ -53,13 +52,10 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
final newAlbumDescription = albumViewer.editDescriptionText;
|
||||
final isEditAlbum = albumViewer.isEditAlbum;
|
||||
|
||||
final comments = album.shared
|
||||
? ref.watch(activityStatisticsProvider(album.remoteId!))
|
||||
: 0;
|
||||
final comments = album.shared ? ref.watch(activityStatisticsProvider(album.remoteId!)) : 0;
|
||||
|
||||
deleteAlbum() async {
|
||||
final bool success =
|
||||
await ref.watch(albumProvider.notifier).deleteAlbum(album);
|
||||
final bool success = await ref.watch(albumProvider.notifier).deleteAlbum(album);
|
||||
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
|
||||
@@ -112,8 +108,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
}
|
||||
|
||||
void onLeaveAlbumPressed() async {
|
||||
bool isSuccess =
|
||||
await ref.watch(albumProvider.notifier).leaveAlbum(album);
|
||||
bool isSuccess = await ref.watch(albumProvider.notifier).leaveAlbum(album);
|
||||
|
||||
if (isSuccess) {
|
||||
context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]));
|
||||
@@ -152,8 +147,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
}
|
||||
|
||||
void onSortOrderToggled() async {
|
||||
final updatedAlbum =
|
||||
await ref.read(albumProvider.notifier).toggleSortOrder(album);
|
||||
final updatedAlbum = await ref.read(albumProvider.notifier).toggleSortOrder(album);
|
||||
|
||||
if (updatedAlbum == null) {
|
||||
ImmichToast.show(
|
||||
@@ -241,8 +235,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
children: [
|
||||
...buildBottomSheetActions(),
|
||||
if (onAddPhotos != null) ...commonActions,
|
||||
if (onAddPhotos != null && userId == album.ownerId)
|
||||
...ownerActions,
|
||||
if (onAddPhotos != null && userId == album.ownerId) ...ownerActions,
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -281,9 +274,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
return IconButton(
|
||||
onPressed: () async {
|
||||
if (newAlbumTitle.isNotEmpty) {
|
||||
bool isSuccess = await ref
|
||||
.watch(albumViewerProvider.notifier)
|
||||
.changeAlbumTitle(album, newAlbumTitle);
|
||||
bool isSuccess = await ref.watch(albumViewerProvider.notifier).changeAlbumTitle(album, newAlbumTitle);
|
||||
if (!isSuccess) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -294,9 +285,8 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
}
|
||||
titleFocusNode.unfocus();
|
||||
} else if (newAlbumDescription.isNotEmpty) {
|
||||
bool isSuccessDescription = await ref
|
||||
.watch(albumViewerProvider.notifier)
|
||||
.changeAlbumDescription(album, newAlbumDescription);
|
||||
bool isSuccessDescription =
|
||||
await ref.watch(albumViewerProvider.notifier).changeAlbumDescription(album, newAlbumDescription);
|
||||
if (!isSuccessDescription) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
@@ -330,8 +320,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
leading: buildLeadingButton(),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
if (album.shared && (album.activityEnabled || comments != 0))
|
||||
buildActivitiesButton(),
|
||||
if (album.shared && (album.activityEnabled || comments != 0)) buildActivitiesButton(),
|
||||
if (album.isRemote) ...[
|
||||
IconButton(
|
||||
splashRadius: 25,
|
||||
|
||||
@@ -19,15 +19,13 @@ class AlbumViewerEditableDescription extends HookConsumerWidget {
|
||||
final albumViewerState = ref.watch(albumViewerProvider);
|
||||
|
||||
final descriptionTextEditController = useTextEditingController(
|
||||
text: albumViewerState.isEditAlbum &&
|
||||
albumViewerState.editDescriptionText.isNotEmpty
|
||||
text: albumViewerState.isEditAlbum && albumViewerState.editDescriptionText.isNotEmpty
|
||||
? albumViewerState.editDescriptionText
|
||||
: albumDescription,
|
||||
);
|
||||
|
||||
void onFocusModeChange() {
|
||||
if (!descriptionFocusNode.hasFocus &&
|
||||
descriptionTextEditController.text.isEmpty) {
|
||||
if (!descriptionFocusNode.hasFocus && descriptionTextEditController.text.isEmpty) {
|
||||
ref.watch(albumViewerProvider.notifier).setEditDescriptionText("");
|
||||
descriptionTextEditController.text = "";
|
||||
}
|
||||
@@ -49,9 +47,7 @@ class AlbumViewerEditableDescription extends HookConsumerWidget {
|
||||
onChanged: (value) {
|
||||
if (value.isEmpty) {
|
||||
} else {
|
||||
ref
|
||||
.watch(albumViewerProvider.notifier)
|
||||
.setEditDescriptionText(value);
|
||||
ref.watch(albumViewerProvider.notifier).setEditDescriptionText(value);
|
||||
}
|
||||
},
|
||||
focusNode: descriptionFocusNode,
|
||||
@@ -62,9 +58,7 @@ class AlbumViewerEditableDescription extends HookConsumerWidget {
|
||||
onTap: () {
|
||||
context.focusScope.requestFocus(descriptionFocusNode);
|
||||
|
||||
ref
|
||||
.watch(albumViewerProvider.notifier)
|
||||
.setEditDescriptionText(albumDescription);
|
||||
ref.watch(albumViewerProvider.notifier).setEditDescriptionText(albumDescription);
|
||||
ref.watch(albumViewerProvider.notifier).enableEditAlbum();
|
||||
|
||||
if (descriptionTextEditController.text == '') {
|
||||
|
||||
@@ -19,8 +19,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
|
||||
final albumViewerState = ref.watch(albumViewerProvider);
|
||||
|
||||
final titleTextEditController = useTextEditingController(
|
||||
text: albumViewerState.isEditAlbum &&
|
||||
albumViewerState.editTitleText.isNotEmpty
|
||||
text: albumViewerState.isEditAlbum && albumViewerState.editTitleText.isNotEmpty
|
||||
? albumViewerState.editTitleText
|
||||
: albumName,
|
||||
);
|
||||
|
||||
@@ -16,8 +16,7 @@ class RemoteAlbumSharedUserIcons extends ConsumerWidget {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final sharedUsersAsync =
|
||||
ref.watch(remoteAlbumSharedUsersProvider(currentAlbum.id));
|
||||
final sharedUsersAsync = ref.watch(remoteAlbumSharedUsersProvider(currentAlbum.id));
|
||||
|
||||
return sharedUsersAsync.maybeWhen(
|
||||
data: (sharedUsers) {
|
||||
|
||||
@@ -65,8 +65,7 @@ class _AssetDragRegionState extends State<AssetDragRegion> {
|
||||
Widget build(BuildContext context) {
|
||||
return RawGestureDetector(
|
||||
gestures: {
|
||||
_CustomLongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<
|
||||
_CustomLongPressGestureRecognizer>(
|
||||
_CustomLongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<_CustomLongPressGestureRecognizer>(
|
||||
() => _CustomLongPressGestureRecognizer(),
|
||||
_registerCallbacks,
|
||||
),
|
||||
@@ -89,9 +88,7 @@ class _AssetDragRegionState extends State<AssetDragRegion> {
|
||||
final local = box.globalToLocal(position);
|
||||
if (!box.hitTest(hitTestResult, position: local)) return null;
|
||||
|
||||
return (hitTestResult.path
|
||||
.firstWhereOrNull((hit) => hit.target is _AssetIndexProxy)
|
||||
?.target as _AssetIndexProxy?)
|
||||
return (hitTestResult.path.firstWhereOrNull((hit) => hit.target is _AssetIndexProxy)?.target as _AssetIndexProxy?)
|
||||
?.index;
|
||||
}
|
||||
|
||||
@@ -99,8 +96,7 @@ class _AssetDragRegionState extends State<AssetDragRegion> {
|
||||
/// Calculate widget height and scroll offset when long press starting instead of in [initState]
|
||||
/// or [didChangeDependencies] as the grid might still be rendering into view to get the actual size
|
||||
final height = context.size?.height;
|
||||
if (height != null &&
|
||||
(topScrollOffset == null || bottomScrollOffset == null)) {
|
||||
if (height != null && (topScrollOffset == null || bottomScrollOffset == null)) {
|
||||
topScrollOffset = height * scrollOffset;
|
||||
bottomScrollOffset = height - topScrollOffset!;
|
||||
}
|
||||
@@ -188,8 +184,7 @@ class AssetIndexWrapper extends SingleChildRenderObjectWidget {
|
||||
// ignore: library_private_types_in_public_api
|
||||
_AssetIndexProxy renderObject,
|
||||
) {
|
||||
renderObject.index =
|
||||
AssetIndex(rowIndex: rowIndex, sectionIndex: sectionIndex);
|
||||
renderObject.index = AssetIndex(rowIndex: rowIndex, sectionIndex: sectionIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,8 +53,7 @@ class RenderList {
|
||||
/// global offset of assets in [_buf]
|
||||
int _bufOffset = 0;
|
||||
|
||||
RenderList(this.elements, this.query, this.allAssets)
|
||||
: totalAssets = allAssets?.length ?? query!.countSync();
|
||||
RenderList(this.elements, this.query, this.allAssets) : totalAssets = allAssets?.length ?? query!.countSync();
|
||||
|
||||
bool get isEmpty => totalAssets == 0;
|
||||
|
||||
@@ -90,9 +89,7 @@ class RenderList {
|
||||
// a tiny bit resulting in a another required load from the DB
|
||||
final start = max(
|
||||
0,
|
||||
forward
|
||||
? offset - oppositeSize
|
||||
: (len > batchSize ? offset : offset + count - len),
|
||||
forward ? offset - oppositeSize : (len > batchSize ? offset : offset + count - len),
|
||||
);
|
||||
// load the calculated batch (start:start+len) from the DB and put it into the buffer
|
||||
_buf = query!.offset(start).limit(len).findAllSync();
|
||||
@@ -156,9 +153,7 @@ class RenderList {
|
||||
: null;
|
||||
|
||||
for (int i = 0; i < total; i += sectionSize) {
|
||||
final date = assets != null
|
||||
? assets[i].fileCreatedAt
|
||||
: await dateLoader?.getDate(i);
|
||||
final date = assets != null ? assets[i].fileCreatedAt : await dateLoader?.getDate(i);
|
||||
|
||||
final int count = i + sectionSize > total ? total - i : sectionSize;
|
||||
if (date == null) break;
|
||||
@@ -175,11 +170,8 @@ class RenderList {
|
||||
return RenderList(elements, query, assets);
|
||||
}
|
||||
|
||||
final formatSameYear =
|
||||
groupBy == GroupAssetsBy.month ? DateFormat.MMMM() : DateFormat.MMMEd();
|
||||
final formatOtherYear = groupBy == GroupAssetsBy.month
|
||||
? DateFormat.yMMMM()
|
||||
: DateFormat.yMMMEd();
|
||||
final formatSameYear = groupBy == GroupAssetsBy.month ? DateFormat.MMMM() : DateFormat.MMMEd();
|
||||
final formatOtherYear = groupBy == GroupAssetsBy.month ? DateFormat.yMMMM() : DateFormat.yMMMEd();
|
||||
final currentYear = DateTime.now().year;
|
||||
final formatMergedSameYear = DateFormat.MMMd();
|
||||
final formatMergedOtherYear = DateFormat.yMMMd();
|
||||
@@ -193,16 +185,9 @@ class RenderList {
|
||||
int lastMonthIndex = 0;
|
||||
|
||||
String formatDateRange(DateTime from, DateTime to) {
|
||||
final startDate = (from.year == currentYear
|
||||
? formatMergedSameYear
|
||||
: formatMergedOtherYear)
|
||||
.format(from);
|
||||
final endDate = (to.year == currentYear
|
||||
? formatMergedSameYear
|
||||
: formatMergedOtherYear)
|
||||
.format(to);
|
||||
if (DateTime(from.year, from.month, from.day) ==
|
||||
DateTime(to.year, to.month, to.day)) {
|
||||
final startDate = (from.year == currentYear ? formatMergedSameYear : formatMergedOtherYear).format(from);
|
||||
final endDate = (to.year == currentYear ? formatMergedSameYear : formatMergedOtherYear).format(to);
|
||||
if (DateTime(from.year, from.month, from.day) == DateTime(to.year, to.month, to.day)) {
|
||||
// format range with time when both dates are on the same day
|
||||
final startTime = DateFormat.Hm().format(from);
|
||||
final endTime = DateFormat.Hm().format(to);
|
||||
@@ -212,10 +197,7 @@ class RenderList {
|
||||
}
|
||||
|
||||
void mergeMonth() {
|
||||
if (last != null &&
|
||||
groupBy == GroupAssetsBy.auto &&
|
||||
monthCount <= 30 &&
|
||||
elements.length > lastMonthIndex + 1) {
|
||||
if (last != null && groupBy == GroupAssetsBy.auto && monthCount <= 30 && elements.length > lastMonthIndex + 1) {
|
||||
// merge all days into a single section
|
||||
assert(elements[lastMonthIndex].date.month == last.month);
|
||||
final e = elements[lastMonthIndex];
|
||||
@@ -233,8 +215,7 @@ class RenderList {
|
||||
}
|
||||
|
||||
void addElems(DateTime d, DateTime? prevDate) {
|
||||
final bool newMonth =
|
||||
last == null || last.year != d.year || last.month != d.month;
|
||||
final bool newMonth = last == null || last.year != d.year || last.month != d.month;
|
||||
if (newMonth) {
|
||||
mergeMonth();
|
||||
lastMonthIndex = elements.length;
|
||||
@@ -258,12 +239,8 @@ class RenderList {
|
||||
totalCount: groupBy == GroupAssetsBy.auto ? sectionCount : count,
|
||||
offset: lastOffset + j,
|
||||
title: j == 0
|
||||
? (d.year == currentYear
|
||||
? formatSameYear.format(d)
|
||||
: formatOtherYear.format(d))
|
||||
: (groupBy == GroupAssetsBy.auto
|
||||
? formatDateRange(d, prevDate ?? d)
|
||||
: null),
|
||||
? (d.year == currentYear ? formatSameYear.format(d) : formatOtherYear.format(d))
|
||||
: (groupBy == GroupAssetsBy.auto ? formatDateRange(d, prevDate ?? d) : null),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -277,11 +254,7 @@ class RenderList {
|
||||
// TODO replace with groupBy once Isar supports such queries
|
||||
final dates = assets != null
|
||||
? assets.map((a) => a.fileCreatedAt)
|
||||
: await query!
|
||||
.offset(offset)
|
||||
.limit(pageSize)
|
||||
.fileCreatedAtProperty()
|
||||
.findAll();
|
||||
: await query!.offset(offset).limit(pageSize).fileCreatedAtProperty().findAll();
|
||||
int i = 0;
|
||||
for (final date in dates) {
|
||||
final d = DateTime(
|
||||
@@ -357,11 +330,7 @@ class DateBatchLoader {
|
||||
Future<void> _loadBatch(int targetIndex) async {
|
||||
final batchStart = (targetIndex ~/ batchSize) * batchSize;
|
||||
|
||||
_buffer = await query
|
||||
.offset(batchStart)
|
||||
.limit(batchSize)
|
||||
.fileCreatedAtProperty()
|
||||
.findAll();
|
||||
_buffer = await query.offset(batchStart).limit(batchSize).fileCreatedAtProperty().findAll();
|
||||
|
||||
_bufferStart = batchStart;
|
||||
}
|
||||
|
||||
@@ -71,15 +71,11 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final hasRemote =
|
||||
selectionAssetState.hasRemote || selectionAssetState.hasMerged;
|
||||
final hasLocal =
|
||||
selectionAssetState.hasLocal || selectionAssetState.hasMerged;
|
||||
final trashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
final hasRemote = selectionAssetState.hasRemote || selectionAssetState.hasMerged;
|
||||
final hasLocal = selectionAssetState.hasLocal || selectionAssetState.hasMerged;
|
||||
final trashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
final albums = ref.watch(albumProvider).where((a) => a.isRemote).toList();
|
||||
final sharedAlbums =
|
||||
ref.watch(albumProvider).where((a) => a.shared).toList();
|
||||
final sharedAlbums = ref.watch(albumProvider).where((a) => a.shared).toList();
|
||||
const bottomPadding = 0.24;
|
||||
final scrollController = useDraggableScrollController();
|
||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||
@@ -132,9 +128,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
List<Widget> renderActionButtons() {
|
||||
return [
|
||||
ControlBoxButton(
|
||||
iconData: Platform.isAndroid
|
||||
? Icons.share_rounded
|
||||
: Icons.ios_share_rounded,
|
||||
iconData: Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded,
|
||||
label: "share".tr(),
|
||||
onPressed: enabled ? () => onShare(true) : null,
|
||||
),
|
||||
@@ -146,16 +140,13 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
),
|
||||
if (hasRemote && onArchive != null)
|
||||
ControlBoxButton(
|
||||
iconData:
|
||||
unarchive ? Icons.unarchive_outlined : Icons.archive_outlined,
|
||||
iconData: unarchive ? Icons.unarchive_outlined : Icons.archive_outlined,
|
||||
label: (unarchive ? "unarchive" : "archive").tr(),
|
||||
onPressed: enabled ? onArchive : null,
|
||||
),
|
||||
if (hasRemote && onFavorite != null)
|
||||
ControlBoxButton(
|
||||
iconData: unfavorite
|
||||
? Icons.favorite_border_rounded
|
||||
: Icons.favorite_rounded,
|
||||
iconData: unfavorite ? Icons.favorite_border_rounded : Icons.favorite_rounded,
|
||||
label: (unfavorite ? "unfavorite" : "favorite").tr(),
|
||||
onPressed: enabled ? onFavorite : null,
|
||||
),
|
||||
@@ -174,11 +165,8 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
child: ControlBoxButton(
|
||||
iconData: Icons.delete_sweep_outlined,
|
||||
label: "delete".tr(),
|
||||
onPressed: enabled
|
||||
? () => handleRemoteDelete(!trashEnabled, onDelete!)
|
||||
: null,
|
||||
onLongPressed:
|
||||
enabled ? () => showForceDeleteDialog(onDelete!) : null,
|
||||
onPressed: enabled ? () => handleRemoteDelete(!trashEnabled, onDelete!) : null,
|
||||
onLongPressed: enabled ? () => showForceDeleteDialog(onDelete!) : null,
|
||||
),
|
||||
),
|
||||
if (hasRemote && onDeleteServer != null && !isInLockedView)
|
||||
@@ -264,18 +252,12 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 100),
|
||||
child: ControlBoxButton(
|
||||
iconData: isInLockedView
|
||||
? Icons.lock_open_rounded
|
||||
: Icons.lock_outline_rounded,
|
||||
label: isInLockedView
|
||||
? "remove_from_locked_folder".tr()
|
||||
: "move_to_locked_folder".tr(),
|
||||
iconData: isInLockedView ? Icons.lock_open_rounded : Icons.lock_outline_rounded,
|
||||
label: isInLockedView ? "remove_from_locked_folder".tr() : "move_to_locked_folder".tr(),
|
||||
onPressed: enabled ? onToggleLocked : null,
|
||||
),
|
||||
),
|
||||
if (!selectionAssetState.hasLocal &&
|
||||
selectionAssetState.selectedCount > 1 &&
|
||||
onStack != null)
|
||||
if (!selectionAssetState.hasLocal && selectionAssetState.selectedCount > 1 && onStack != null)
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 90),
|
||||
child: ControlBoxButton(
|
||||
|
||||
@@ -276,8 +276,7 @@ class ScrollLabel extends StatelessWidget {
|
||||
final Text child;
|
||||
|
||||
final BoxConstraints? constraints;
|
||||
static const BoxConstraints _defaultConstraints =
|
||||
BoxConstraints.tightFor(width: 72.0, height: 28.0);
|
||||
static const BoxConstraints _defaultConstraints = BoxConstraints.tightFor(width: 72.0, height: 28.0);
|
||||
|
||||
const ScrollLabel({
|
||||
super.key,
|
||||
@@ -308,8 +307,7 @@ class ScrollLabel extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
with TickerProviderStateMixin {
|
||||
class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
|
||||
late double _barOffset;
|
||||
late double _viewOffset;
|
||||
late bool _isDragInProcess;
|
||||
@@ -356,8 +354,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double get barMaxScrollExtent =>
|
||||
context.size!.height - widget.heightScrollThumb;
|
||||
double get barMaxScrollExtent => context.size!.height - widget.heightScrollThumb;
|
||||
|
||||
double get barMinScrollExtent => 0;
|
||||
|
||||
@@ -447,8 +444,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
}
|
||||
}
|
||||
|
||||
if (notification is ScrollUpdateNotification ||
|
||||
notification is OverscrollNotification) {
|
||||
if (notification is ScrollUpdateNotification || notification is OverscrollNotification) {
|
||||
if (_thumbAnimationController.status != AnimationStatus.forward) {
|
||||
_thumbAnimationController.forward();
|
||||
}
|
||||
@@ -627,8 +623,7 @@ class SlideFadeTransition extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, child) =>
|
||||
animation.value == 0.0 ? const SizedBox() : child!,
|
||||
builder: (context, child) => animation.value == 0.0 ? const SizedBox() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0.3, 0.0),
|
||||
|
||||
@@ -169,8 +169,7 @@ class ScrollLabel extends StatelessWidget {
|
||||
final Text child;
|
||||
|
||||
final BoxConstraints? constraints;
|
||||
static const BoxConstraints _defaultConstraints =
|
||||
BoxConstraints.tightFor(width: 72.0, height: 28.0);
|
||||
static const BoxConstraints _defaultConstraints = BoxConstraints.tightFor(width: 72.0, height: 28.0);
|
||||
|
||||
const ScrollLabel({
|
||||
super.key,
|
||||
@@ -202,8 +201,7 @@ class ScrollLabel extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
with TickerProviderStateMixin {
|
||||
class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProviderStateMixin {
|
||||
late double _barOffset;
|
||||
late bool _isDragInProcess;
|
||||
late int _currentItem;
|
||||
@@ -250,10 +248,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double get barMaxScrollExtent =>
|
||||
(context.size?.height ?? 0) -
|
||||
widget.heightScrollThumb -
|
||||
(widget.heightOffset ?? 0);
|
||||
double get barMaxScrollExtent => (context.size?.height ?? 0) - widget.heightScrollThumb - (widget.heightOffset ?? 0);
|
||||
|
||||
double get barMinScrollExtent => 0;
|
||||
|
||||
@@ -317,8 +312,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
|
||||
setState(() {
|
||||
try {
|
||||
int firstItemIndex =
|
||||
widget.itemPositionsListener.itemPositions.value.first.index;
|
||||
int firstItemIndex = widget.itemPositionsListener.itemPositions.value.first.index;
|
||||
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
_barOffset = (firstItemIndex / maxItemCount) * barMaxScrollExtent;
|
||||
@@ -331,8 +325,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
|
||||
}
|
||||
}
|
||||
|
||||
if (notification is ScrollUpdateNotification ||
|
||||
notification is OverscrollNotification) {
|
||||
if (notification is ScrollUpdateNotification || notification is OverscrollNotification) {
|
||||
if (_thumbAnimationController.status != AnimationStatus.forward) {
|
||||
_thumbAnimationController.forward();
|
||||
}
|
||||
@@ -536,8 +529,7 @@ class SlideFadeTransition extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, child) =>
|
||||
animation.value == 0.0 ? const SizedBox() : child!,
|
||||
builder: (context, child) => animation.value == 0.0 ? const SizedBox() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0.3, 0.0),
|
||||
|
||||
@@ -32,8 +32,7 @@ class GroupDividerTitle extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
groupBy.value = GroupAssetsBy.values[
|
||||
appSettingService.getSetting<int>(AppSettingsEnum.groupAssetsBy)];
|
||||
groupBy.value = GroupAssetsBy.values[appSettingService.getSetting<int>(AppSettingsEnum.groupAssetsBy)];
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
@@ -75,14 +74,12 @@ class GroupDividerTitle extends HookConsumerWidget {
|
||||
? Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel:
|
||||
"unselect_all_in".tr(namedArgs: {"group": text}),
|
||||
semanticLabel: "unselect_all_in".tr(namedArgs: {"group": text}),
|
||||
)
|
||||
: Icon(
|
||||
Icons.check_circle_outline_rounded,
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
semanticLabel:
|
||||
"select_all_in".tr(namedArgs: {"group": text}),
|
||||
semanticLabel: "select_all_in".tr(namedArgs: {"group": text}),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -27,8 +27,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
||||
final bool canDeselect;
|
||||
final bool? dynamicLayout;
|
||||
final bool showMultiSelectIndicator;
|
||||
final void Function(Iterable<ItemPosition> itemPositions)?
|
||||
visibleItemsListener;
|
||||
final void Function(Iterable<ItemPosition> itemPositions)? visibleItemsListener;
|
||||
final Widget? topWidget;
|
||||
final bool shrinkWrap;
|
||||
final bool showDragScroll;
|
||||
@@ -82,10 +81,8 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
||||
Widget buildAssetGridView(RenderList renderList) {
|
||||
return RawGestureDetector(
|
||||
gestures: {
|
||||
CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
|
||||
CustomScaleGestureRecognizer>(
|
||||
() => CustomScaleGestureRecognizer(),
|
||||
(CustomScaleGestureRecognizer scale) {
|
||||
CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomScaleGestureRecognizer>(
|
||||
() => CustomScaleGestureRecognizer(), (CustomScaleGestureRecognizer scale) {
|
||||
scale.onStart = (details) {
|
||||
baseScaleFactor.value = scaleFactor.value;
|
||||
};
|
||||
@@ -106,15 +103,13 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
||||
onRefresh: onRefresh,
|
||||
assetsPerRow: perRow.value,
|
||||
listener: listener,
|
||||
showStorageIndicator: showStorageIndicator ??
|
||||
settings.getSetting(AppSettingsEnum.storageIndicator),
|
||||
showStorageIndicator: showStorageIndicator ?? settings.getSetting(AppSettingsEnum.storageIndicator),
|
||||
renderList: renderList,
|
||||
margin: margin,
|
||||
selectionActive: selectionActive,
|
||||
preselectedAssets: preselectedAssets,
|
||||
canDeselect: canDeselect,
|
||||
dynamicLayout: dynamicLayout ??
|
||||
settings.getSetting(AppSettingsEnum.dynamicLayout),
|
||||
dynamicLayout: dynamicLayout ?? settings.getSetting(AppSettingsEnum.dynamicLayout),
|
||||
showMultiSelectIndicator: showMultiSelectIndicator,
|
||||
visibleItemsListener: visibleItemsListener,
|
||||
topWidget: topWidget,
|
||||
|
||||
@@ -51,8 +51,7 @@ class ImmichAssetGridView extends ConsumerStatefulWidget {
|
||||
final bool canDeselect;
|
||||
final bool dynamicLayout;
|
||||
final bool showMultiSelectIndicator;
|
||||
final void Function(Iterable<ItemPosition> itemPositions)?
|
||||
visibleItemsListener;
|
||||
final void Function(Iterable<ItemPosition> itemPositions)? visibleItemsListener;
|
||||
final Widget? topWidget;
|
||||
final int heroOffset;
|
||||
final bool shrinkWrap;
|
||||
@@ -90,24 +89,20 @@ class ImmichAssetGridView extends ConsumerStatefulWidget {
|
||||
|
||||
class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
final ItemScrollController _itemScrollController = ItemScrollController();
|
||||
final ScrollOffsetController _scrollOffsetController =
|
||||
ScrollOffsetController();
|
||||
final ItemPositionsListener _itemPositionsListener =
|
||||
ItemPositionsListener.create();
|
||||
final ScrollOffsetController _scrollOffsetController = ScrollOffsetController();
|
||||
final ItemPositionsListener _itemPositionsListener = ItemPositionsListener.create();
|
||||
late final KeepAliveLink currentAssetLink;
|
||||
|
||||
/// The timestamp when the haptic feedback was last invoked
|
||||
int _hapticFeedbackTS = 0;
|
||||
DateTime? _prevItemTime;
|
||||
bool _scrolling = false;
|
||||
final Set<Asset> _selectedAssets =
|
||||
LinkedHashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id);
|
||||
final Set<Asset> _selectedAssets = LinkedHashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id);
|
||||
|
||||
bool _dragging = false;
|
||||
int? _dragAnchorAssetIndex;
|
||||
int? _dragAnchorSectionIndex;
|
||||
final Set<Asset> _draggedAssets =
|
||||
HashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id);
|
||||
final Set<Asset> _draggedAssets = HashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id);
|
||||
|
||||
ScrollPhysics? _scrollPhysics;
|
||||
|
||||
@@ -131,9 +126,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
|
||||
void _deselectAssets(List<Asset> assets) {
|
||||
final assetsToDeselect = assets.where(
|
||||
(a) =>
|
||||
widget.canDeselect ||
|
||||
!(widget.preselectedAssets?.contains(a) ?? false),
|
||||
(a) => widget.canDeselect || !(widget.preselectedAssets?.contains(a) ?? false),
|
||||
);
|
||||
|
||||
setState(() {
|
||||
@@ -152,9 +145,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
_dragAnchorSectionIndex = null;
|
||||
_draggedAssets.clear();
|
||||
_dragging = false;
|
||||
if (!widget.canDeselect &&
|
||||
widget.preselectedAssets != null &&
|
||||
widget.preselectedAssets!.isNotEmpty) {
|
||||
if (!widget.canDeselect && widget.preselectedAssets != null && widget.preselectedAssets!.isNotEmpty) {
|
||||
_selectedAssets.addAll(widget.preselectedAssets!);
|
||||
}
|
||||
_callSelectionListener(false);
|
||||
@@ -162,8 +153,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
}
|
||||
|
||||
bool _allAssetsSelected(List<Asset> assets) {
|
||||
return widget.selectionActive &&
|
||||
assets.firstWhereOrNull((e) => !_selectedAssets.contains(e)) == null;
|
||||
return widget.selectionActive && assets.firstWhereOrNull((e) => !_selectedAssets.contains(e)) == null;
|
||||
}
|
||||
|
||||
Future<void> _scrollToIndex(int index) async {
|
||||
@@ -244,8 +234,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
}
|
||||
|
||||
Widget _buildAssetGrid() {
|
||||
final useDragScrolling =
|
||||
widget.showDragScroll && widget.renderList.totalAssets >= 20;
|
||||
final useDragScrolling = widget.showDragScroll && widget.renderList.totalAssets >= 20;
|
||||
|
||||
void dragScrolling(bool active) {
|
||||
if (active != _scrolling) {
|
||||
@@ -256,9 +245,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
}
|
||||
|
||||
bool appBarOffset() {
|
||||
return (ref.watch(tabProvider).index == 0 &&
|
||||
ModalRoute.of(context)?.settings.name ==
|
||||
TabControllerRoute.name) ||
|
||||
return (ref.watch(tabProvider).index == 0 && ModalRoute.of(context)?.settings.name == TabControllerRoute.name) ||
|
||||
(ModalRoute.of(context)?.settings.name == AlbumViewerRoute.name);
|
||||
}
|
||||
|
||||
@@ -272,8 +259,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
physics: _scrollPhysics,
|
||||
itemScrollController: _itemScrollController,
|
||||
scrollOffsetController: _scrollOffsetController,
|
||||
itemCount: widget.renderList.elements.length +
|
||||
(widget.topWidget != null ? 1 : 0),
|
||||
itemCount: widget.renderList.elements.length + (widget.topWidget != null ? 1 : 0),
|
||||
addRepaintBoundaries: true,
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
);
|
||||
@@ -283,13 +269,10 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
scrollStateListener: dragScrolling,
|
||||
itemPositionsListener: _itemPositionsListener,
|
||||
controller: _itemScrollController,
|
||||
backgroundColor: context.isDarkTheme
|
||||
? context.colorScheme.primary.darken(amount: .5)
|
||||
: context.colorScheme.primary,
|
||||
backgroundColor:
|
||||
context.isDarkTheme ? context.colorScheme.primary.darken(amount: .5) : context.colorScheme.primary,
|
||||
labelTextBuilder: widget.showLabel ? _labelBuilder : null,
|
||||
padding: appBarOffset()
|
||||
? const EdgeInsets.only(top: 60)
|
||||
: const EdgeInsets.only(),
|
||||
padding: appBarOffset() ? const EdgeInsets.only(top: 60) : const EdgeInsets.only(),
|
||||
heightOffset: appBarOffset() ? 60 : 0,
|
||||
labelConstraints: const BoxConstraints(maxHeight: 28),
|
||||
scrollbarAnimationDuration: const Duration(milliseconds: 300),
|
||||
@@ -323,10 +306,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
|
||||
// Search for the index of the exact date in the list
|
||||
var index = widget.renderList.elements.indexWhere(
|
||||
(e) =>
|
||||
e.date.year == date.year &&
|
||||
e.date.month == date.month &&
|
||||
e.date.day == date.day,
|
||||
(e) => e.date.year == date.year && e.date.month == date.month && e.date.day == date.day,
|
||||
);
|
||||
|
||||
// If the exact date is not found, the timeline is grouped by month,
|
||||
@@ -343,8 +323,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
} else {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg:
|
||||
"The date (${DateFormat.yMd().format(date)}) could not be found in the timeline.",
|
||||
msg: "The date (${DateFormat.yMd().format(date)}) could not be found in the timeline.",
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
@@ -417,8 +396,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
// on startup.
|
||||
if (_prevItemTime == null) {
|
||||
_prevItemTime = date;
|
||||
} else if (_prevItemTime?.year != date.year ||
|
||||
_prevItemTime?.month != date.month) {
|
||||
} else if (_prevItemTime?.year != date.year || _prevItemTime?.month != date.month) {
|
||||
_prevItemTime = date;
|
||||
|
||||
final now = Timeline.now;
|
||||
@@ -511,12 +489,10 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
final selectedAssets = <Asset>{};
|
||||
var currentSectionIndex = startSectionIndex;
|
||||
while (currentSectionIndex < endSectionIndex) {
|
||||
final section =
|
||||
widget.renderList.elements.elementAtOrNull(currentSectionIndex);
|
||||
final section = widget.renderList.elements.elementAtOrNull(currentSectionIndex);
|
||||
if (section == null) continue;
|
||||
|
||||
final sectionAssets =
|
||||
widget.renderList.loadAssets(section.offset, section.count);
|
||||
final sectionAssets = widget.renderList.loadAssets(section.offset, section.count);
|
||||
|
||||
if (currentSectionIndex == startSectionIndex) {
|
||||
selectedAssets.addAll(
|
||||
@@ -531,8 +507,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
|
||||
final section = widget.renderList.elements.elementAtOrNull(endSectionIndex);
|
||||
if (section != null) {
|
||||
final sectionAssets =
|
||||
widget.renderList.loadAssets(section.offset, section.count);
|
||||
final sectionAssets = widget.renderList.loadAssets(section.offset, section.count);
|
||||
if (startSectionIndex == endSectionIndex) {
|
||||
selectedAssets.addAll(
|
||||
sectionAssets.slice(startSectionAssetIndex, endSectionAssetIndex + 1),
|
||||
@@ -562,8 +537,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
/// "add to album" button.
|
||||
///
|
||||
/// `_selectedAssets` includes `preselectedAssets` on initialization.
|
||||
if (_selectedAssets.length >
|
||||
(widget.preselectedAssets?.length ?? 0)) {
|
||||
if (_selectedAssets.length > (widget.preselectedAssets?.length ?? 0)) {
|
||||
/// `_deselectAll` only deselects the selected assets,
|
||||
/// doesn't affect the preselected ones.
|
||||
_deselectAll();
|
||||
@@ -585,8 +559,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
),
|
||||
child: _buildAssetGrid(),
|
||||
),
|
||||
if (widget.showMultiSelectIndicator && widget.selectionActive)
|
||||
_buildMultiSelectIndicator(),
|
||||
if (widget.showMultiSelectIndicator && widget.selectionActive) _buildMultiSelectIndicator(),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -671,26 +644,20 @@ class _Section extends StatelessWidget {
|
||||
) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth / assetsPerRow -
|
||||
margin * (assetsPerRow - 1) / assetsPerRow;
|
||||
final width = constraints.maxWidth / assetsPerRow - margin * (assetsPerRow - 1) / assetsPerRow;
|
||||
final rows = (section.count + assetsPerRow - 1) ~/ assetsPerRow;
|
||||
final List<Asset> assetsToRender = scrolling
|
||||
? []
|
||||
: renderList.loadAssets(section.offset, section.count);
|
||||
final List<Asset> assetsToRender = scrolling ? [] : renderList.loadAssets(section.offset, section.count);
|
||||
return Column(
|
||||
key: ValueKey(section.offset),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (section.type == RenderAssetGridElementType.monthTitle)
|
||||
_MonthTitle(date: section.date),
|
||||
if (section.type == RenderAssetGridElementType.monthTitle) _MonthTitle(date: section.date),
|
||||
if (section.type == RenderAssetGridElementType.groupDividerTitle ||
|
||||
section.type == RenderAssetGridElementType.monthTitle)
|
||||
_Title(
|
||||
selectionActive: selectionActive,
|
||||
title: section.title!,
|
||||
assets: scrolling
|
||||
? []
|
||||
: renderList.loadAssets(section.offset, section.totalCount),
|
||||
assets: scrolling ? [] : renderList.loadAssets(section.offset, section.totalCount),
|
||||
allAssetsSelected: allAssetsSelected,
|
||||
selectAssets: selectAssets,
|
||||
deselectAssets: deselectAssets,
|
||||
@@ -699,9 +666,7 @@ class _Section extends StatelessWidget {
|
||||
scrolling
|
||||
? _PlaceholderRow(
|
||||
key: ValueKey(i),
|
||||
number: i + 1 == rows
|
||||
? section.count - i * assetsPerRow
|
||||
: assetsPerRow,
|
||||
number: i + 1 == rows ? section.count - i * assetsPerRow : assetsPerRow,
|
||||
width: width,
|
||||
height: width,
|
||||
margin: margin,
|
||||
@@ -747,9 +712,7 @@ class _MonthTitle extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final monthFormat = DateTime.now().year == date.year
|
||||
? DateFormat.MMMM()
|
||||
: DateFormat.yMMMM();
|
||||
final monthFormat = DateTime.now().year == date.year ? DateFormat.MMMM() : DateFormat.yMMMM();
|
||||
final String title = monthFormat.format(date);
|
||||
return Padding(
|
||||
key: Key("month-$title"),
|
||||
@@ -844,8 +807,7 @@ class _AssetRow extends StatelessWidget {
|
||||
final widthDistribution = List.filled(assets.length, 1.0);
|
||||
|
||||
if (dynamicLayout) {
|
||||
final aspectRatios =
|
||||
assets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList();
|
||||
final aspectRatios = assets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList();
|
||||
final meanAspectRatio = aspectRatios.sum / assets.length;
|
||||
|
||||
// 1: mean width
|
||||
|
||||
@@ -65,11 +65,9 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
final bool unfavorite;
|
||||
final bool editEnabled;
|
||||
final Widget? emptyIndicator;
|
||||
Widget buildDefaultLoadingIndicator() =>
|
||||
const Center(child: CircularProgressIndicator());
|
||||
Widget buildDefaultLoadingIndicator() => const Center(child: CircularProgressIndicator());
|
||||
|
||||
Widget buildEmptyIndicator() =>
|
||||
emptyIndicator ?? Center(child: const Text("no_assets_to_show").tr());
|
||||
Widget buildEmptyIndicator() => emptyIndicator ?? Center(child: const Text("no_assets_to_show").tr());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -103,8 +101,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
) {
|
||||
selectionEnabledHook.value = multiselect;
|
||||
selection.value = selectedAssets;
|
||||
selectionAssetState.value =
|
||||
AssetSelectionState.fromSelection(selectedAssets);
|
||||
selectionAssetState.value = AssetSelectionState.fromSelection(selectedAssets);
|
||||
}
|
||||
|
||||
errorBuilder(String? msg) => msg != null && msg.isNotEmpty
|
||||
@@ -120,16 +117,13 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
String? ownerErrorMessage,
|
||||
}) {
|
||||
final assets = selection.value;
|
||||
return assets
|
||||
.remoteOnly(errorCallback: errorBuilder(localErrorMessage))
|
||||
.ownedOnly(
|
||||
return assets.remoteOnly(errorCallback: errorBuilder(localErrorMessage)).ownedOnly(
|
||||
currentUser,
|
||||
errorCallback: errorBuilder(ownerErrorMessage),
|
||||
);
|
||||
}
|
||||
|
||||
Iterable<Asset> remoteSelection({String? errorMessage}) =>
|
||||
selection.value.remoteOnly(
|
||||
Iterable<Asset> remoteSelection({String? errorMessage}) => selection.value.remoteOnly(
|
||||
errorCallback: errorBuilder(errorMessage),
|
||||
);
|
||||
|
||||
@@ -139,9 +133,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
// Share = Download + Send to OS specific share sheet
|
||||
handleShareAssets(ref, context, selection.value);
|
||||
} else {
|
||||
final ids =
|
||||
remoteSelection(errorMessage: "home_page_share_err_local".tr())
|
||||
.map((e) => e.remoteId!);
|
||||
final ids = remoteSelection(errorMessage: "home_page_share_err_local".tr()).map((e) => e.remoteId!);
|
||||
context.pushRoute(SharedLinkEditRoute(assetsList: ids.toList()));
|
||||
}
|
||||
processing.value = false;
|
||||
@@ -187,18 +179,14 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
errorCallback: errorBuilder('home_page_delete_err_partner'.tr()),
|
||||
)
|
||||
.toList();
|
||||
final isDeleted = await ref
|
||||
.read(assetProvider.notifier)
|
||||
.deleteAssets(toDelete, force: force);
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(toDelete, force: force);
|
||||
|
||||
if (isDeleted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: force
|
||||
? 'assets_deleted_permanently'
|
||||
.tr(namedArgs: {'count': "${selection.value.length}"})
|
||||
: 'assets_trashed'
|
||||
.tr(namedArgs: {'count': "${selection.value.length}"}),
|
||||
? 'assets_deleted_permanently'.tr(namedArgs: {'count': "${selection.value.length}"})
|
||||
: 'assets_trashed'.tr(namedArgs: {'count': "${selection.value.length}"}),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
selectionEnabledHook.value = false;
|
||||
@@ -213,26 +201,20 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
try {
|
||||
final localAssets = selection.value.where((a) => a.isLocal).toList();
|
||||
|
||||
final toDelete = isMergedAsset
|
||||
? localAssets.where((e) => e.storage == AssetState.merged)
|
||||
: localAssets;
|
||||
final toDelete = isMergedAsset ? localAssets.where((e) => e.storage == AssetState.merged) : localAssets;
|
||||
|
||||
if (toDelete.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final isDeleted = await ref
|
||||
.read(assetProvider.notifier)
|
||||
.deleteLocalAssets(toDelete.toList());
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteLocalAssets(toDelete.toList());
|
||||
|
||||
if (isDeleted) {
|
||||
final deletedCount =
|
||||
localAssets.where((e) => !isMergedAsset || e.isRemote).length;
|
||||
final deletedCount = localAssets.where((e) => !isMergedAsset || e.isRemote).length;
|
||||
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'assets_removed_permanently_from_device'
|
||||
.tr(namedArgs: {'count': "$deletedCount"}),
|
||||
msg: 'assets_removed_permanently_from_device'.tr(namedArgs: {'count': "$deletedCount"}),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
|
||||
@@ -248,9 +230,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
try {
|
||||
final toDownload = selection.value.toList();
|
||||
|
||||
final results = await ref
|
||||
.read(downloadStateProvider.notifier)
|
||||
.downloadAllAsset(toDownload);
|
||||
final results = await ref.read(downloadStateProvider.notifier).downloadAllAsset(toDownload);
|
||||
|
||||
final totalCount = toDownload.length;
|
||||
final successCount = results.where((e) => e).length;
|
||||
@@ -290,19 +270,16 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
ownerErrorMessage: 'home_page_delete_err_partner'.tr(),
|
||||
).toList();
|
||||
|
||||
final isDeleted =
|
||||
await ref.read(assetProvider.notifier).deleteRemoteAssets(
|
||||
toDelete,
|
||||
shouldDeletePermanently: shouldDeletePermanently,
|
||||
);
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteRemoteAssets(
|
||||
toDelete,
|
||||
shouldDeletePermanently: shouldDeletePermanently,
|
||||
);
|
||||
if (isDeleted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: shouldDeletePermanently
|
||||
? 'assets_deleted_permanently_from_server'
|
||||
.tr(namedArgs: {'count': "${toDelete.length}"})
|
||||
: 'assets_trashed_from_server'
|
||||
.tr(namedArgs: {'count': "${toDelete.length}"}),
|
||||
? 'assets_deleted_permanently_from_server'.tr(namedArgs: {'count': "${toDelete.length}"})
|
||||
: 'assets_trashed_from_server'.tr(namedArgs: {'count': "${toDelete.length}"}),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
@@ -379,9 +356,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
if (assets.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final result = await ref
|
||||
.read(albumServiceProvider)
|
||||
.createAlbumWithGeneratedName(assets);
|
||||
final result = await ref.read(albumServiceProvider).createAlbumWithGeneratedName(assets);
|
||||
|
||||
if (result != null) {
|
||||
ref.watch(albumProvider.notifier).refreshRemoteAlbums();
|
||||
@@ -449,9 +424,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
);
|
||||
if (remoteAssets.isNotEmpty) {
|
||||
final isInLockedView = ref.read(inLockedViewProvider);
|
||||
final visibility = isInLockedView
|
||||
? AssetVisibilityEnum.timeline
|
||||
: AssetVisibilityEnum.locked;
|
||||
final visibility = isInLockedView ? AssetVisibilityEnum.timeline : AssetVisibilityEnum.locked;
|
||||
|
||||
await handleSetAssetsVisibility(
|
||||
ref,
|
||||
@@ -489,8 +462,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
ref.watch(renderListProvider).when(
|
||||
data: (data) => data.isEmpty &&
|
||||
(buildLoadingIndicator != null || topWidget == null)
|
||||
data: (data) => data.isEmpty && (buildLoadingIndicator != null || topWidget == null)
|
||||
? (buildLoadingIndicator ?? buildEmptyIndicator)()
|
||||
: ImmichAssetGrid(
|
||||
renderList: data,
|
||||
|
||||
@@ -25,10 +25,8 @@ class MultiselectGridStatusIndicator extends HookConsumerWidget {
|
||||
),
|
||||
)
|
||||
: buildLoadingIndicator!(),
|
||||
RenderListStatusEnum.empty =>
|
||||
emptyIndicator ?? Center(child: const Text("no_assets_to_show").tr()),
|
||||
RenderListStatusEnum.error =>
|
||||
Center(child: const Text("error_loading_assets").tr()),
|
||||
RenderListStatusEnum.empty => emptyIndicator ?? Center(child: const Text("no_assets_to_show").tr()),
|
||||
RenderListStatusEnum.error => Center(child: const Text("error_loading_assets").tr()),
|
||||
RenderListStatusEnum.complete => const SizedBox()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,9 +41,8 @@ class ThumbnailImage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final assetContainerColor = context.isDarkTheme
|
||||
? context.primaryColor.darken(amount: 0.6)
|
||||
: context.primaryColor.lighten(amount: 0.8);
|
||||
final assetContainerColor =
|
||||
context.isDarkTheme ? context.primaryColor.darken(amount: 0.6) : context.primaryColor.lighten(amount: 0.8);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
@@ -118,9 +117,8 @@ class _SelectedIcon extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final assetContainerColor = context.isDarkTheme
|
||||
? context.primaryColor.darken(amount: 0.6)
|
||||
: context.primaryColor.lighten(amount: 0.8);
|
||||
final assetContainerColor =
|
||||
context.isDarkTheme ? context.primaryColor.darken(amount: 0.6) : context.primaryColor.lighten(amount: 0.8);
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
@@ -322,9 +320,7 @@ class _ImageIcon extends StatelessWidget {
|
||||
}
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: canDeselect
|
||||
? BoxDecoration(color: assetContainerColor)
|
||||
: const BoxDecoration(color: Colors.grey),
|
||||
decoration: canDeselect ? BoxDecoration(color: assetContainerColor) : const BoxDecoration(color: Colors.grey),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15.0)),
|
||||
child: image,
|
||||
|
||||
@@ -35,9 +35,7 @@ class AdvancedBottomSheet extends HookConsumerWidget {
|
||||
const SizedBox(height: 32.0),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.isDarkTheme
|
||||
? Colors.grey[900]
|
||||
: Colors.grey[200],
|
||||
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[200],
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15.0),
|
||||
),
|
||||
@@ -66,8 +64,7 @@ class AdvancedBottomSheet extends HookConsumerWidget {
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Copied to clipboard",
|
||||
style:
|
||||
context.textTheme.bodyLarge?.copyWith(
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -17,8 +17,7 @@ class AnimatedPlayPause extends StatefulWidget {
|
||||
State<StatefulWidget> createState() => AnimatedPlayPauseState();
|
||||
}
|
||||
|
||||
class AnimatedPlayPauseState extends State<AnimatedPlayPause>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class AnimatedPlayPauseState extends State<AnimatedPlayPause> with SingleTickerProviderStateMixin {
|
||||
late final animationController = AnimationController(
|
||||
vsync: this,
|
||||
value: widget.playing ? 1 : 0,
|
||||
|
||||
@@ -52,28 +52,21 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
if (asset == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
final isOwner =
|
||||
asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? '');
|
||||
final isOwner = asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? '');
|
||||
final showControls = ref.watch(showControlsProvider);
|
||||
final stackId = asset.stackId;
|
||||
|
||||
final stackItems = showStack && stackId != null
|
||||
? ref.watch(assetStackStateProvider(stackId))
|
||||
: <Asset>[];
|
||||
final stackItems = showStack && stackId != null ? ref.watch(assetStackStateProvider(stackId)) : <Asset>[];
|
||||
bool isStackPrimaryAsset = asset.stackPrimaryAssetId == null;
|
||||
final navStack = AutoRouter.of(context).stackData;
|
||||
final isTrashEnabled =
|
||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
final isFromTrash = isTrashEnabled &&
|
||||
navStack.length > 2 &&
|
||||
navStack.elementAt(navStack.length - 2).name == TrashRoute.name;
|
||||
final isTrashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
|
||||
final isFromTrash =
|
||||
isTrashEnabled && navStack.length > 2 && navStack.elementAt(navStack.length - 2).name == TrashRoute.name;
|
||||
final isInAlbum = ref.watch(currentAlbumProvider)?.isRemote ?? false;
|
||||
|
||||
void removeAssetFromStack() {
|
||||
if (stackIndex.value > 0 && showStack && stackId != null) {
|
||||
ref
|
||||
.read(assetStackStateProvider(stackId).notifier)
|
||||
.removeChild(stackIndex.value - 1);
|
||||
ref.read(assetStackStateProvider(stackId).notifier).removeChild(stackIndex.value - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +82,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
|
||||
// `assetIndex == totalAssets.value - 1` handle the case of removing the last asset
|
||||
// to not throw the error when the next preCache index is called
|
||||
if (totalAssets.value == 1 ||
|
||||
assetIndex.value == totalAssets.value - 1) {
|
||||
if (totalAssets.value == 1 || assetIndex.value == totalAssets.value - 1) {
|
||||
// Handle only one asset
|
||||
context.maybePop();
|
||||
}
|
||||
@@ -98,9 +90,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
totalAssets.value -= 1;
|
||||
}
|
||||
if (isDeleted) {
|
||||
ref
|
||||
.read(currentAssetProvider.notifier)
|
||||
.set(renderList.loadAsset(assetIndex.value));
|
||||
ref.read(currentAssetProvider.notifier).set(renderList.loadAsset(assetIndex.value));
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
@@ -144,9 +134,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref
|
||||
.read(stackServiceProvider)
|
||||
.deleteStack(asset.stackId!, stackItems);
|
||||
await ref.read(stackServiceProvider).deleteStack(asset.stackId!, stackItems);
|
||||
}
|
||||
|
||||
void showStackActionItems() {
|
||||
@@ -240,8 +228,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
|
||||
handleRemoveFromAlbum() async {
|
||||
final album = ref.read(currentAlbumProvider);
|
||||
final bool isSuccess = album != null &&
|
||||
await ref.read(albumProvider.notifier).removeAsset(album, [asset]);
|
||||
final bool isSuccess = album != null && await ref.read(albumProvider.notifier).removeAsset(album, [asset]);
|
||||
|
||||
if (isSuccess) {
|
||||
// Workaround for asset remaining in the gallery
|
||||
@@ -373,9 +360,7 @@ class BottomGalleryBar extends ConsumerWidget {
|
||||
unselectedItemColor: Colors.white,
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: true,
|
||||
items: albumActions
|
||||
.map((e) => e.keys.first)
|
||||
.toList(growable: false),
|
||||
items: albumActions.map((e) => e.keys.first).toList(growable: false),
|
||||
onTap: (index) {
|
||||
albumActions[index].values.first.call(index);
|
||||
},
|
||||
|
||||
@@ -49,10 +49,8 @@ class CastDialog extends ConsumerWidget {
|
||||
}
|
||||
|
||||
final devices = snapshot.data!;
|
||||
final connected =
|
||||
devices.where((d) => isCurrentDevice(d.$1)).toList();
|
||||
final others =
|
||||
devices.where((d) => !isCurrentDevice(d.$1)).toList();
|
||||
final connected = devices.where((d) => isCurrentDevice(d.$1)).toList();
|
||||
final others = devices.where((d) => !isCurrentDevice(d.$1)).toList();
|
||||
|
||||
final List<dynamic> sectionedList = [];
|
||||
|
||||
@@ -85,25 +83,18 @@ class CastDialog extends ConsumerWidget {
|
||||
).tr(),
|
||||
);
|
||||
} else {
|
||||
final (deviceName, type, deviceObj) =
|
||||
item as (String, CastDestinationType, dynamic);
|
||||
final (deviceName, type, deviceObj) = item as (String, CastDestinationType, dynamic);
|
||||
|
||||
return ListTile(
|
||||
title: Text(
|
||||
deviceName,
|
||||
style: TextStyle(
|
||||
color: isCurrentDevice(deviceName)
|
||||
? context.colorScheme.primary
|
||||
: null,
|
||||
color: isCurrentDevice(deviceName) ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
type == CastDestinationType.googleCast
|
||||
? Icons.cast
|
||||
: Icons.cast_connected,
|
||||
color: isCurrentDevice(deviceName)
|
||||
? context.colorScheme.primary
|
||||
: null,
|
||||
type == CastDestinationType.googleCast ? Icons.cast : Icons.cast_connected,
|
||||
color: isCurrentDevice(deviceName) ? context.colorScheme.primary : null,
|
||||
),
|
||||
trailing: isCurrentDevice(deviceName)
|
||||
? Icon(Icons.check, color: context.colorScheme.primary)
|
||||
@@ -120,9 +111,7 @@ class CastDialog extends ConsumerWidget {
|
||||
}
|
||||
|
||||
if (!isCurrentDevice(deviceName)) {
|
||||
ref
|
||||
.read(castProvider.notifier)
|
||||
.connect(type, deviceObj);
|
||||
ref.read(castProvider.notifier).connect(type, deviceObj);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -24,8 +24,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
||||
currentAssetProvider.select((asset) => asset != null && asset.isVideo),
|
||||
);
|
||||
final showControls = ref.watch(showControlsProvider);
|
||||
final VideoPlaybackState state =
|
||||
ref.watch(videoPlaybackValueProvider.select((value) => value.state));
|
||||
final VideoPlaybackState state = ref.watch(videoPlaybackValueProvider.select((value) => value.state));
|
||||
|
||||
final cast = ref.watch(castProvider);
|
||||
|
||||
@@ -39,15 +38,12 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
||||
final state = ref.read(videoPlaybackValueProvider).state;
|
||||
|
||||
// Do not hide on paused
|
||||
if (state != VideoPlaybackState.paused &&
|
||||
state != VideoPlaybackState.completed &&
|
||||
assetIsVideo) {
|
||||
if (state != VideoPlaybackState.paused && state != VideoPlaybackState.completed && assetIsVideo) {
|
||||
ref.read(showControlsProvider.notifier).show = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
final showBuffering =
|
||||
state == VideoPlaybackState.buffering && !cast.isCasting;
|
||||
final showBuffering = state == VideoPlaybackState.buffering && !cast.isCasting;
|
||||
|
||||
/// Shows the controls and starts the timer to hide them
|
||||
void showControlsAndStartHideTimer() {
|
||||
@@ -56,8 +52,7 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
// When we change position, show or hide timer
|
||||
ref.listen(videoPlayerControlsProvider.select((v) => v.position),
|
||||
(previous, next) {
|
||||
ref.listen(videoPlayerControlsProvider.select((v) => v.position), (previous, next) {
|
||||
showControlsAndStartHideTimer();
|
||||
});
|
||||
|
||||
@@ -105,14 +100,13 @@ class CustomVideoPlayerControls extends HookConsumerWidget {
|
||||
)
|
||||
else
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
ref.read(showControlsProvider.notifier).show = false,
|
||||
onTap: () => ref.read(showControlsProvider.notifier).show = false,
|
||||
child: CenterPlayButton(
|
||||
backgroundColor: Colors.black54,
|
||||
iconColor: Colors.white,
|
||||
isFinished: state == VideoPlaybackState.completed,
|
||||
isPlaying: state == VideoPlaybackState.playing ||
|
||||
(cast.isCasting && cast.castState == CastState.playing),
|
||||
isPlaying:
|
||||
state == VideoPlaybackState.playing || (cast.isCasting && cast.castState == CastState.playing),
|
||||
show: assetIsVideo && showControls,
|
||||
onPressed: togglePlay,
|
||||
),
|
||||
|
||||
@@ -24,10 +24,7 @@ class CameraInfo extends StatelessWidget {
|
||||
"${exifInfo.make} ${exifInfo.model}",
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
subtitle: exifInfo.f != null ||
|
||||
exifInfo.exposureSeconds != null ||
|
||||
exifInfo.mm != null ||
|
||||
exifInfo.iso != null
|
||||
subtitle: exifInfo.f != null || exifInfo.exposureSeconds != null || exifInfo.mm != null || exifInfo.iso != null
|
||||
? Text(
|
||||
"ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO ${exifInfo.iso ?? ''} ",
|
||||
style: context.textTheme.bodySmall,
|
||||
|
||||
@@ -24,9 +24,7 @@ class DetailPanel extends HookConsumerWidget {
|
||||
child: Column(
|
||||
children: [
|
||||
AssetDateTime(asset: asset),
|
||||
asset.isRemote
|
||||
? DescriptionInput(asset: asset)
|
||||
: const SizedBox.shrink(),
|
||||
asset.isRemote ? DescriptionInput(asset: asset) : const SizedBox.shrink(),
|
||||
PeopleInfo(asset: asset),
|
||||
AssetLocation(asset: asset),
|
||||
AssetDetails(asset: asset),
|
||||
|
||||
@@ -17,11 +17,8 @@ class FileInfo extends StatelessWidget {
|
||||
|
||||
final height = asset.orientatedHeight ?? asset.height;
|
||||
final width = asset.orientatedWidth ?? asset.width;
|
||||
String resolution =
|
||||
height != null && width != null ? "$width x $height " : "";
|
||||
String fileSize = asset.exifInfo?.fileSize != null
|
||||
? formatBytes(asset.exifInfo!.fileSize!)
|
||||
: "";
|
||||
String resolution = height != null && width != null ? "$width x $height " : "";
|
||||
String fileSize = asset.exifInfo?.fileSize != null ? formatBytes(asset.exifInfo!.fileSize!) : "";
|
||||
String text = resolution + fileSize;
|
||||
final imgSizeString = text.isNotEmpty ? text : null;
|
||||
|
||||
|
||||
@@ -18,12 +18,8 @@ class PeopleInfo extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final peopleProvider =
|
||||
ref.watch(assetPeopleNotifierProvider(asset).notifier);
|
||||
final people = ref
|
||||
.watch(assetPeopleNotifierProvider(asset))
|
||||
.value
|
||||
?.where((p) => !p.isHidden);
|
||||
final peopleProvider = ref.watch(assetPeopleNotifierProvider(asset).notifier);
|
||||
final people = ref.watch(assetPeopleNotifierProvider(asset)).value?.where((p) => !p.isHidden);
|
||||
|
||||
showPersonNameEditModel(
|
||||
String personId,
|
||||
@@ -46,8 +42,7 @@ class PeopleInfo extends ConsumerWidget {
|
||||
(p) => SearchCuratedContent(
|
||||
id: p.id,
|
||||
label: p.name,
|
||||
subtitle: p.birthDate != null &&
|
||||
p.birthDate!.isBefore(asset.fileCreatedAt)
|
||||
subtitle: p.birthDate != null && p.birthDate!.isBefore(asset.fileCreatedAt)
|
||||
? _formatAge(p.birthDate!, asset.fileCreatedAt)
|
||||
: null,
|
||||
),
|
||||
@@ -56,9 +51,7 @@ class PeopleInfo extends ConsumerWidget {
|
||||
[];
|
||||
|
||||
return AnimatedCrossFade(
|
||||
crossFadeState: (people?.isEmpty ?? true)
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
crossFadeState: (people?.isEmpty ?? true) ? CrossFadeState.showFirst : CrossFadeState.showSecond,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
firstChild: Container(),
|
||||
secondChild: Padding(
|
||||
@@ -109,22 +102,18 @@ class PeopleInfo extends ConsumerWidget {
|
||||
int ageInMonths = _calculateAgeInMonths(birthDate, referenceDate);
|
||||
|
||||
if (ageInMonths <= 11) {
|
||||
return "exif_bottom_sheet_person_age_months"
|
||||
.tr(namedArgs: {'months': ageInMonths.toString()});
|
||||
return "exif_bottom_sheet_person_age_months".tr(namedArgs: {'months': ageInMonths.toString()});
|
||||
} else if (ageInMonths > 12 && ageInMonths <= 23) {
|
||||
return "exif_bottom_sheet_person_age_year_months"
|
||||
.tr(namedArgs: {'months': (ageInMonths - 12).toString()});
|
||||
return "exif_bottom_sheet_person_age_year_months".tr(namedArgs: {'months': (ageInMonths - 12).toString()});
|
||||
} else {
|
||||
return "exif_bottom_sheet_person_age_years"
|
||||
.tr(namedArgs: {'years': ageInYears.toString()});
|
||||
return "exif_bottom_sheet_person_age_years".tr(namedArgs: {'years': ageInYears.toString()});
|
||||
}
|
||||
}
|
||||
|
||||
int _calculateAge(DateTime birthDate, DateTime referenceDate) {
|
||||
int age = referenceDate.year - birthDate.year;
|
||||
if (referenceDate.month < birthDate.month ||
|
||||
(referenceDate.month == birthDate.month &&
|
||||
referenceDate.day < birthDate.day)) {
|
||||
(referenceDate.month == birthDate.month && referenceDate.day < birthDate.day)) {
|
||||
age--;
|
||||
}
|
||||
return age;
|
||||
|
||||
@@ -34,17 +34,12 @@ class GalleryAppBar extends ConsumerWidget {
|
||||
return const SizedBox();
|
||||
}
|
||||
final album = ref.watch(currentAlbumProvider);
|
||||
final isOwner =
|
||||
asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? '');
|
||||
final isOwner = asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? '');
|
||||
final showControls = ref.watch(showControlsProvider);
|
||||
|
||||
final isPartner = ref
|
||||
.watch(partnerSharedWithProvider)
|
||||
.map((e) => fastHash(e.id))
|
||||
.contains(asset.ownerId);
|
||||
final isPartner = ref.watch(partnerSharedWithProvider).map((e) => fastHash(e.id)).contains(asset.ownerId);
|
||||
|
||||
toggleFavorite(Asset asset) =>
|
||||
ref.read(assetProvider.notifier).toggleFavorite([asset]);
|
||||
toggleFavorite(Asset asset) => ref.read(assetProvider.notifier).toggleFavorite([asset]);
|
||||
|
||||
handleActivities() {
|
||||
if (album != null && album.shared && album.remoteId != null) {
|
||||
@@ -53,8 +48,7 @@ class GalleryAppBar extends ConsumerWidget {
|
||||
}
|
||||
|
||||
handleRestore(Asset asset) async {
|
||||
final result =
|
||||
await ref.read(trashProvider.notifier).restoreAssets([asset]);
|
||||
final result = await ref.read(trashProvider.notifier).restoreAssets([asset]);
|
||||
|
||||
if (result && context.mounted) {
|
||||
ImmichToast.show(
|
||||
@@ -71,9 +65,7 @@ class GalleryAppBar extends ConsumerWidget {
|
||||
builder: (BuildContext _) {
|
||||
return UploadDialog(
|
||||
onUpload: () {
|
||||
ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.uploadAssets(context, [asset]);
|
||||
ref.read(manualUploadProvider.notifier).uploadAssets(context, [asset]);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -104,8 +96,7 @@ class GalleryAppBar extends ConsumerWidget {
|
||||
handleLocateAsset() async {
|
||||
// Go back to the gallery
|
||||
await context.maybePop();
|
||||
await context
|
||||
.navigateTo(const TabControllerRoute(children: [PhotosRoute()]));
|
||||
await context.navigateTo(const TabControllerRoute(children: [PhotosRoute()]));
|
||||
ref.read(tabProvider.notifier).update((state) => state = TabEnum.home);
|
||||
// Scroll to the asset's date
|
||||
scrollToDateNotifierProvider.scrollToDate(asset.fileCreatedAt);
|
||||
|
||||
@@ -49,12 +49,9 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||
final a = ref.watch(assetWatcher(asset)).value ?? asset;
|
||||
final album = ref.watch(currentAlbumProvider);
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
final websocketConnected =
|
||||
ref.watch(websocketProvider.select((c) => c.isConnected));
|
||||
final websocketConnected = ref.watch(websocketProvider.select((c) => c.isConnected));
|
||||
|
||||
final comments = album != null &&
|
||||
album.remoteId != null &&
|
||||
asset.remoteId != null
|
||||
final comments = album != null && album.remoteId != null && asset.remoteId != null
|
||||
? ref.watch(activityStatisticsProvider(album.remoteId!, asset.remoteId))
|
||||
: 0;
|
||||
|
||||
@@ -204,24 +201,14 @@ class TopControlAppBar extends HookConsumerWidget {
|
||||
shape: const Border(),
|
||||
actions: [
|
||||
if (asset.isRemote && isOwner) buildFavoriteButton(a),
|
||||
if (isOwner &&
|
||||
!isInHomePage &&
|
||||
!(isInTrash ?? false) &&
|
||||
!isInLockedView)
|
||||
buildLocateButton(),
|
||||
if (isOwner && !isInHomePage && !(isInTrash ?? false) && !isInLockedView) buildLocateButton(),
|
||||
if (asset.livePhotoVideoId != null) const MotionPhotoButton(),
|
||||
if (asset.isLocal && !asset.isRemote) buildUploadButton(),
|
||||
if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(),
|
||||
if (asset.isRemote &&
|
||||
(isOwner || isPartner) &&
|
||||
!asset.isTrashed &&
|
||||
!isInLockedView)
|
||||
buildAddToAlbumButton(),
|
||||
if (isCasting || (asset.isRemote && websocketConnected))
|
||||
buildCastButton(),
|
||||
if (asset.isRemote && (isOwner || isPartner) && !asset.isTrashed && !isInLockedView) buildAddToAlbumButton(),
|
||||
if (isCasting || (asset.isRemote && websocketConnected)) buildCastButton(),
|
||||
if (asset.isTrashed) buildRestoreButton(),
|
||||
if (album != null && album.shared && !isInLockedView)
|
||||
buildActivitiesButton(),
|
||||
if (album != null && album.shared && !isInLockedView) buildActivitiesButton(),
|
||||
buildMoreInfoButton(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -54,8 +54,7 @@ class VideoPosition extends HookConsumerWidget {
|
||||
activeColor: Colors.white,
|
||||
inactiveColor: whiteOpacity75,
|
||||
onChangeStart: (value) {
|
||||
final state =
|
||||
ref.read(videoPlaybackValueProvider).state;
|
||||
final state = ref.read(videoPlaybackValueProvider).state;
|
||||
wasPlaying.value = state != VideoPlaybackState.paused;
|
||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||
},
|
||||
@@ -68,19 +67,14 @@ class VideoPosition extends HookConsumerWidget {
|
||||
final seekToDuration = (duration * (value / 100.0));
|
||||
|
||||
if (isCasting) {
|
||||
ref
|
||||
.read(castProvider.notifier)
|
||||
.seekTo(seekToDuration);
|
||||
ref.read(castProvider.notifier).seekTo(seekToDuration);
|
||||
return;
|
||||
}
|
||||
|
||||
ref
|
||||
.read(videoPlayerControlsProvider.notifier)
|
||||
.position = seekToDuration.inSeconds.toDouble();
|
||||
ref.read(videoPlayerControlsProvider.notifier).position = seekToDuration.inSeconds.toDouble();
|
||||
|
||||
// This immediately updates the slider position without waiting for the video to update
|
||||
ref.read(videoPlaybackValueProvider.notifier).position =
|
||||
seekToDuration;
|
||||
ref.read(videoPlaybackValueProvider.notifier).position = seekToDuration;
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -23,13 +23,9 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bool isSelected =
|
||||
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||
final bool isExcluded =
|
||||
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||
final syncAlbum = ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.syncAlbums);
|
||||
final bool isSelected = ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||
final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||
final syncAlbum = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
|
||||
@@ -37,10 +33,8 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
context.primaryColor.withAlpha(100),
|
||||
BlendMode.darken,
|
||||
);
|
||||
ColorFilter excludedFilter =
|
||||
ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
|
||||
ColorFilter unselectedFilter =
|
||||
const ColorFilter.mode(Colors.black, BlendMode.color);
|
||||
ColorFilter excludedFilter = ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
|
||||
ColorFilter unselectedFilter = const ColorFilter.mode(Colors.black, BlendMode.color);
|
||||
|
||||
buildSelectedTextBox() {
|
||||
if (isSelected) {
|
||||
@@ -133,9 +127,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
Radius.circular(12), // if you need this
|
||||
),
|
||||
side: BorderSide(
|
||||
color: isDarkTheme
|
||||
? const Color.fromARGB(255, 37, 35, 35)
|
||||
: const Color(0xFFC9C9C9),
|
||||
color: isDarkTheme ? const Color.fromARGB(255, 37, 35, 35) : const Color(0xFFC9C9C9),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
@@ -190,8 +182,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2.0),
|
||||
child: Text(
|
||||
album.assetCount.toString() +
|
||||
(album.isAll ? " (${'all'.tr()})" : ""),
|
||||
album.assetCount.toString() + (album.isAll ? " (${'all'.tr()})" : ""),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
|
||||
@@ -19,23 +19,15 @@ class AlbumInfoListTile extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final bool isSelected =
|
||||
ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||
final bool isExcluded =
|
||||
ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||
final syncAlbum = ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.syncAlbums);
|
||||
final bool isSelected = ref.watch(backupProvider).selectedBackupAlbums.contains(album);
|
||||
final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(album);
|
||||
final syncAlbum = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||
|
||||
buildTileColor() {
|
||||
if (isSelected) {
|
||||
return context.isDarkTheme
|
||||
? context.primaryColor.withAlpha(100)
|
||||
: context.primaryColor.withAlpha(25);
|
||||
return context.isDarkTheme ? context.primaryColor.withAlpha(100) : context.primaryColor.withAlpha(25);
|
||||
} else if (isExcluded) {
|
||||
return context.isDarkTheme
|
||||
? Colors.red[300]?.withAlpha(150)
|
||||
: Colors.red[100]?.withAlpha(150);
|
||||
return context.isDarkTheme ? Colors.red[300]?.withAlpha(150) : Colors.red[100]?.withAlpha(150);
|
||||
} else {
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
@@ -82,9 +82,7 @@ class BackupAssetInfoTable extends ConsumerWidget {
|
||||
),
|
||||
).tr(
|
||||
namedArgs: {
|
||||
'date': isUploadInProgress
|
||||
? _getAssetCreationDate(asset)
|
||||
: "-",
|
||||
'date': isUploadInProgress ? _getAssetCreationDate(asset) : "-",
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -22,19 +22,13 @@ class DriftAlbumInfoListTile extends HookConsumerWidget {
|
||||
final bool isSelected = album.backupSelection == BackupSelection.selected;
|
||||
final bool isExcluded = album.backupSelection == BackupSelection.excluded;
|
||||
|
||||
final syncAlbum = ref
|
||||
.watch(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.syncAlbums);
|
||||
final syncAlbum = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||
|
||||
buildTileColor() {
|
||||
if (isSelected) {
|
||||
return context.isDarkTheme
|
||||
? context.primaryColor.withAlpha(100)
|
||||
: context.primaryColor.withAlpha(25);
|
||||
return context.isDarkTheme ? context.primaryColor.withAlpha(100) : context.primaryColor.withAlpha(25);
|
||||
} else if (isExcluded) {
|
||||
return context.isDarkTheme
|
||||
? Colors.red[300]?.withAlpha(150)
|
||||
: Colors.red[100]?.withAlpha(150);
|
||||
return context.isDarkTheme ? Colors.red[300]?.withAlpha(150) : Colors.red[100]?.withAlpha(150);
|
||||
} else {
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ class BackupErrorChip extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final hasErrors =
|
||||
ref.watch(errorBackupListProvider.select((value) => value.isNotEmpty));
|
||||
final hasErrors = ref.watch(errorBackupListProvider.select((value) => value.isNotEmpty));
|
||||
if (!hasErrors) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
@@ -17,20 +17,17 @@ class IcloudDownloadProgressBar extends ConsumerWidget {
|
||||
|
||||
final isIcloudAsset = isManualUpload
|
||||
? ref.watch(
|
||||
manualUploadProvider
|
||||
.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
manualUploadProvider.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
)
|
||||
: ref.watch(
|
||||
backupProvider
|
||||
.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
backupProvider.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
);
|
||||
|
||||
if (!isIcloudAsset) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final iCloudDownloadProgress = ref
|
||||
.watch(backupProvider.select((value) => value.iCloudDownloadProgress));
|
||||
final iCloudDownloadProgress = ref.watch(backupProvider.select((value) => value.iCloudDownloadProgress));
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
|
||||
@@ -24,8 +24,7 @@ class IosDebugInfoTile extends HookConsumerWidget {
|
||||
if (processes == 0) {
|
||||
title = 'ios_debug_info_no_processes_queued'.t(context: context);
|
||||
} else {
|
||||
title = 'ios_debug_info_processes_queued'
|
||||
.t(context: context, args: {'count': processes});
|
||||
title = 'ios_debug_info_processes_queued'.t(context: context, args: {'count': processes});
|
||||
}
|
||||
|
||||
final df = DateFormat.yMd().add_jm();
|
||||
@@ -33,14 +32,11 @@ class IosDebugInfoTile extends HookConsumerWidget {
|
||||
if (fetch == null && processing == null) {
|
||||
subtitle = 'ios_debug_info_no_sync_yet'.t(context: context);
|
||||
} else if (fetch != null && processing == null) {
|
||||
subtitle = 'ios_debug_info_fetch_ran_at'
|
||||
.t(context: context, args: {'dateTime': df.format(fetch)});
|
||||
subtitle = 'ios_debug_info_fetch_ran_at'.t(context: context, args: {'dateTime': df.format(fetch)});
|
||||
} else if (processing != null && fetch == null) {
|
||||
subtitle = 'ios_debug_info_processing_ran_at'
|
||||
.t(context: context, args: {'dateTime': df.format(processing)});
|
||||
subtitle = 'ios_debug_info_processing_ran_at'.t(context: context, args: {'dateTime': df.format(processing)});
|
||||
} else {
|
||||
final fetchOrProcessing =
|
||||
fetch!.isAfter(processing!) ? fetch : processing;
|
||||
final fetchOrProcessing = fetch!.isAfter(processing!) ? fetch : processing;
|
||||
subtitle = 'ios_debug_info_last_sync_at'.t(
|
||||
context: context,
|
||||
args: {'dateTime': df.format(fetchOrProcessing)},
|
||||
|
||||
@@ -18,12 +18,10 @@ class BackupUploadProgressBar extends ConsumerWidget {
|
||||
|
||||
final isIcloudAsset = isManualUpload
|
||||
? ref.watch(
|
||||
manualUploadProvider
|
||||
.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
manualUploadProvider.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
)
|
||||
: ref.watch(
|
||||
backupProvider
|
||||
.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
backupProvider.select((value) => value.currentUploadAsset.isIcloudAsset),
|
||||
);
|
||||
|
||||
final uploadProgress = isManualUpload
|
||||
|
||||
@@ -58,9 +58,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
|
||||
),
|
||||
Center(
|
||||
child: Image.asset(
|
||||
context.isDarkTheme
|
||||
? 'assets/immich-text-dark.png'
|
||||
: 'assets/immich-text-light.png',
|
||||
context.isDarkTheme ? 'assets/immich-text-dark.png' : 'assets/immich-text-light.png',
|
||||
height: 16,
|
||||
),
|
||||
),
|
||||
@@ -131,10 +129,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
|
||||
ok: "yes",
|
||||
onOk: () async {
|
||||
isLoggingOut.value = true;
|
||||
await ref
|
||||
.read(authProvider.notifier)
|
||||
.logout()
|
||||
.whenComplete(() => isLoggingOut.value = false);
|
||||
await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false);
|
||||
|
||||
ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||
ref.read(backupProvider.notifier).cancelBackup();
|
||||
@@ -196,14 +191,12 @@ class ImmichAppBarDialog extends HookConsumerWidget {
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 10.0,
|
||||
value: percentage,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(10.0)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12.0),
|
||||
child:
|
||||
const Text('backup_controller_page_storage_format').tr(
|
||||
child: const Text('backup_controller_page_storage_format').tr(
|
||||
namedArgs: {
|
||||
'used': usedDiskSpace,
|
||||
'total': totalDiskSpace,
|
||||
|
||||
@@ -17,8 +17,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final authState = ref.watch(authProvider);
|
||||
final uploadProfileImageStatus =
|
||||
ref.watch(uploadProfileImageProvider).status;
|
||||
final uploadProfileImageStatus = ref.watch(uploadProfileImageProvider).status;
|
||||
final user = ref.watch(currentUserProvider);
|
||||
|
||||
buildUserProfileImage() {
|
||||
@@ -55,12 +54,10 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
if (image != null) {
|
||||
var success =
|
||||
await ref.watch(uploadProfileImageProvider.notifier).upload(image);
|
||||
var success = await ref.watch(uploadProfileImageProvider.notifier).upload(image);
|
||||
|
||||
if (success) {
|
||||
final profileImagePath =
|
||||
ref.read(uploadProfileImageProvider).profileImagePath;
|
||||
final profileImagePath = ref.read(uploadProfileImageProvider).profileImagePath;
|
||||
ref.watch(authProvider.notifier).updateUserProfileImagePath(
|
||||
profileImagePath,
|
||||
);
|
||||
|
||||
@@ -173,12 +173,10 @@ class AppBarServerInfo extends HookConsumerWidget {
|
||||
verticalOffset: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor.withValues(alpha: 0.9),
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(10)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
textStyle: TextStyle(
|
||||
color:
|
||||
context.isDarkTheme ? Colors.black : Colors.white,
|
||||
color: context.isDarkTheme ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
message: getServerUrl() ?? '--',
|
||||
|
||||
@@ -73,10 +73,7 @@ class _DateTimePicker extends HookWidget {
|
||||
|
||||
// returns a list of location<name> along with it's offset in duration
|
||||
List<_TimeZoneOffset> getAllTimeZones() {
|
||||
return tz.timeZoneDatabase.locations.values
|
||||
.map(_TimeZoneOffset.fromLocation)
|
||||
.sorted()
|
||||
.toList();
|
||||
return tz.timeZoneDatabase.locations.values.map(_TimeZoneOffset.fromLocation).sorted().toList();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -125,11 +122,9 @@ class _DateTimePicker extends HookWidget {
|
||||
}
|
||||
|
||||
void popWithDateTime() {
|
||||
final formattedDateTime =
|
||||
DateFormat("yyyy-MM-dd'T'HH:mm:ss").format(date.value);
|
||||
final dtWithOffset = formattedDateTime +
|
||||
Duration(milliseconds: tzOffset.value.offsetInMilliseconds)
|
||||
.formatAsOffset();
|
||||
final formattedDateTime = DateFormat("yyyy-MM-dd'T'HH:mm:ss").format(date.value);
|
||||
final dtWithOffset =
|
||||
formattedDateTime + Duration(milliseconds: tzOffset.value.offsetInMilliseconds).formatAsOffset();
|
||||
context.pop(dtWithOffset);
|
||||
}
|
||||
|
||||
@@ -245,19 +240,15 @@ class _TimeZoneOffset implements Comparable<_TimeZoneOffset> {
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'_TimeZoneOffset(display: $display, location: $location)';
|
||||
String toString() => '_TimeZoneOffset(display: $display, location: $location)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is _TimeZoneOffset &&
|
||||
other.display == display &&
|
||||
other.offsetInMilliseconds == offsetInMilliseconds;
|
||||
return other is _TimeZoneOffset && other.display == display && other.offsetInMilliseconds == offsetInMilliseconds;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
display.hashCode ^ offsetInMilliseconds.hashCode ^ location.hashCode;
|
||||
int get hashCode => display.hashCode ^ offsetInMilliseconds.hashCode ^ location.hashCode;
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ class ControlBoxButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final minWidth =
|
||||
context.isMobile ? MediaQuery.sizeOf(context).width / 4.5 : 75.0;
|
||||
final minWidth = context.isMobile ? MediaQuery.sizeOf(context).width / 4.5 : 75.0;
|
||||
|
||||
return MaterialButton(
|
||||
padding: const EdgeInsets.all(10),
|
||||
|
||||
@@ -30,8 +30,7 @@ class DropdownSearchMenu<T> extends HookWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedItem = useState<DropdownMenuEntry<T>?>(
|
||||
dropdownMenuEntries
|
||||
.firstWhereOrNull((item) => item.value == initialSelection),
|
||||
dropdownMenuEntries.firstWhereOrNull((item) => item.value == initialSelection),
|
||||
);
|
||||
final showTimeZoneDropdown = useState<bool>(false);
|
||||
|
||||
@@ -77,10 +76,7 @@ class DropdownSearchMenu<T> extends HookWidget {
|
||||
displayStringForOption: (option) => option.label,
|
||||
optionsBuilder: (textEditingValue) {
|
||||
return dropdownMenuEntries.where(
|
||||
(item) => item.label
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.contains(textEditingValue.text.toLowerCase().trim()),
|
||||
(item) => item.label.toLowerCase().trim().contains(textEditingValue.text.toLowerCase().trim()),
|
||||
);
|
||||
},
|
||||
onSelected: (option) {
|
||||
@@ -127,9 +123,7 @@ class DropdownSearchMenu<T> extends HookWidget {
|
||||
onTap: () => onSelected(option),
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
final bool highlight =
|
||||
AutocompleteHighlightedOption.of(context) ==
|
||||
index;
|
||||
final bool highlight = AutocompleteHighlightedOption.of(context) == index;
|
||||
if (highlight) {
|
||||
SchedulerBinding.instance.addPostFrameCallback(
|
||||
(Duration timeStamp) {
|
||||
@@ -142,12 +136,7 @@ class DropdownSearchMenu<T> extends HookWidget {
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
color: highlight
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withValues(alpha: 0.12)
|
||||
: null,
|
||||
color: highlight ? Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.12) : null,
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
option.label,
|
||||
|
||||
@@ -27,8 +27,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final BackUpState backupState = ref.watch(backupProvider);
|
||||
final bool isEnableAutoBackup =
|
||||
backupState.backgroundBackup || backupState.autoBackup;
|
||||
final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup;
|
||||
final ServerInfo serverInfoState = ref.watch(serverInfoProvider);
|
||||
final user = ref.watch(currentUserProvider);
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
@@ -57,9 +56,8 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
alignment: Alignment.bottomRight,
|
||||
isLabelVisible: serverInfoState.isVersionMismatch ||
|
||||
((user?.isAdmin ?? false) &&
|
||||
serverInfoState.isNewReleaseAvailable),
|
||||
isLabelVisible:
|
||||
serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable),
|
||||
offset: const Offset(-2, -12),
|
||||
child: user == null
|
||||
? const Icon(
|
||||
@@ -92,8 +90,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
semanticsLabel: 'backup_controller_page_backup'.tr(),
|
||||
),
|
||||
);
|
||||
} else if (backupState.backupProgress !=
|
||||
BackUpProgressEnum.inBackground &&
|
||||
} else if (backupState.backupProgress != BackUpProgressEnum.inBackground &&
|
||||
backupState.backupProgress != BackUpProgressEnum.manualInProgress) {
|
||||
return Icon(
|
||||
Icons.check_outlined,
|
||||
|
||||
@@ -59,8 +59,7 @@ class ImmichImage extends StatelessWidget {
|
||||
|
||||
// Whether to use the local asset image provider or a remote one
|
||||
static bool useLocal(Asset asset) =>
|
||||
!asset.isRemote ||
|
||||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false);
|
||||
!asset.isRemote || asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -41,8 +41,7 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
final isMultiSelectEnabled =
|
||||
ref.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
|
||||
return SliverAnimatedOpacity(
|
||||
duration: Durations.medium1,
|
||||
@@ -116,8 +115,7 @@ class _ImmichLogoWithText extends StatelessWidget {
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Badge(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||
backgroundColor: context.primaryColor,
|
||||
alignment: Alignment.centerRight,
|
||||
offset: const Offset(16, -8),
|
||||
@@ -180,8 +178,8 @@ class _ProfileIndicator extends ConsumerWidget {
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
alignment: Alignment.bottomRight,
|
||||
isLabelVisible: serverInfoState.isVersionMismatch ||
|
||||
((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable),
|
||||
isLabelVisible:
|
||||
serverInfoState.isVersionMismatch || ((user?.isAdmin ?? false) && serverInfoState.isNewReleaseAvailable),
|
||||
offset: const Offset(-2, -12),
|
||||
child: user == null
|
||||
? const Icon(
|
||||
@@ -241,8 +239,7 @@ class _BackupIndicator extends ConsumerWidget {
|
||||
|
||||
Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) {
|
||||
final BackUpState backupState = ref.watch(backupProvider);
|
||||
final bool isEnableAutoBackup =
|
||||
backupState.backgroundBackup || backupState.autoBackup;
|
||||
final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup;
|
||||
final isDarkTheme = context.isDarkTheme;
|
||||
final iconColor = isDarkTheme ? Colors.white : Colors.black;
|
||||
|
||||
@@ -257,8 +254,7 @@ class _BackupIndicator extends ConsumerWidget {
|
||||
semanticsLabel: 'backup_controller_page_backup'.tr(),
|
||||
),
|
||||
);
|
||||
} else if (backupState.backupProgress !=
|
||||
BackUpProgressEnum.inBackground &&
|
||||
} else if (backupState.backupProgress != BackUpProgressEnum.inBackground &&
|
||||
backupState.backupProgress != BackUpProgressEnum.manualInProgress) {
|
||||
return Icon(
|
||||
Icons.check_outlined,
|
||||
@@ -286,12 +282,10 @@ class _SyncStatusIndicator extends ConsumerStatefulWidget {
|
||||
const _SyncStatusIndicator();
|
||||
|
||||
@override
|
||||
ConsumerState<_SyncStatusIndicator> createState() =>
|
||||
_SyncStatusIndicatorState();
|
||||
ConsumerState<_SyncStatusIndicator> createState() => _SyncStatusIndicatorState();
|
||||
}
|
||||
|
||||
class _SyncStatusIndicatorState extends ConsumerState<_SyncStatusIndicator>
|
||||
with TickerProviderStateMixin {
|
||||
class _SyncStatusIndicatorState extends ConsumerState<_SyncStatusIndicator> with TickerProviderStateMixin {
|
||||
late AnimationController _rotationController;
|
||||
late AnimationController _dismissalController;
|
||||
late Animation<double> _rotationAnimation;
|
||||
@@ -349,8 +343,7 @@ class _SyncStatusIndicatorState extends ConsumerState<_SyncStatusIndicator>
|
||||
}
|
||||
|
||||
// Don't show anything if not syncing and dismissal animation is complete
|
||||
if (!isSyncing &&
|
||||
_dismissalController.status == AnimationStatus.completed) {
|
||||
if (!isSyncing && _dismissalController.status == AnimationStatus.completed) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
@@ -364,10 +357,7 @@ class _SyncStatusIndicatorState extends ConsumerState<_SyncStatusIndicator>
|
||||
child: Opacity(
|
||||
opacity: isSyncing ? 1.0 : _dismissalAnimation.value,
|
||||
child: Transform.rotate(
|
||||
angle: _rotationAnimation.value *
|
||||
2 *
|
||||
3.14159 *
|
||||
-1, // Rotate counter-clockwise
|
||||
angle: _rotationAnimation.value * 2 * 3.14159 * -1, // Rotate counter-clockwise
|
||||
child: Icon(
|
||||
Icons.sync,
|
||||
size: 24,
|
||||
|
||||
@@ -93,8 +93,7 @@ class ImmichThumbnail extends HookConsumerWidget {
|
||||
customErrorBuilder(BuildContext ctx, Object error, StackTrace? stackTrace) {
|
||||
thumbnailProviderInstance.evict();
|
||||
|
||||
final originalErrorWidgetBuilder =
|
||||
blurHashErrorBuilder(blurhash, fit: fit);
|
||||
final originalErrorWidgetBuilder = blurHashErrorBuilder(blurhash, fit: fit);
|
||||
return originalErrorWidgetBuilder(ctx, error, stackTrace);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@ class ImmichTitleText extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Image(
|
||||
image: AssetImage(
|
||||
context.isDarkTheme
|
||||
? 'assets/immich-text-dark.png'
|
||||
: 'assets/immich-text-light.png',
|
||||
context.isDarkTheme ? 'assets/immich-text-dark.png' : 'assets/immich-text-light.png',
|
||||
),
|
||||
width: fontSize * 4,
|
||||
filterQuality: FilterQuality.high,
|
||||
|
||||
@@ -56,8 +56,7 @@ class _LocationPicker extends HookWidget {
|
||||
? _MapPicker(
|
||||
key: ValueKey(latlng),
|
||||
latlng: latlng,
|
||||
onModeSwitch: () =>
|
||||
pickerMode.value = _LocationPickerMode.manual,
|
||||
onModeSwitch: () => pickerMode.value = _LocationPickerMode.manual,
|
||||
onMapTap: onMapTap,
|
||||
)
|
||||
: _ManualPicker(
|
||||
@@ -141,8 +140,7 @@ class _ManualPickerInput extends HookWidget {
|
||||
errorText: isValid.value ? null : errorText.tr(),
|
||||
),
|
||||
onEditingComplete: onEditingComplete,
|
||||
keyboardType:
|
||||
const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
inputFormatters: [LengthLimitingTextInputFormatter(8)],
|
||||
onTapOutside: (_) => focusNode.unfocus(),
|
||||
);
|
||||
|
||||
@@ -23,12 +23,10 @@ class MesmerizingSliverAppBar extends ConsumerStatefulWidget {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
@override
|
||||
ConsumerState<MesmerizingSliverAppBar> createState() =>
|
||||
_MesmerizingSliverAppBarState();
|
||||
ConsumerState<MesmerizingSliverAppBar> createState() => _MesmerizingSliverAppBarState();
|
||||
}
|
||||
|
||||
class _MesmerizingSliverAppBarState
|
||||
extends ConsumerState<MesmerizingSliverAppBar> {
|
||||
class _MesmerizingSliverAppBarState extends ConsumerState<MesmerizingSliverAppBar> {
|
||||
double _scrollProgress = 0.0;
|
||||
|
||||
double _calculateScrollProgress(FlexibleSpaceBarSettings? settings) {
|
||||
@@ -41,14 +39,12 @@ class _MesmerizingSliverAppBarState
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent)
|
||||
.clamp(0.0, 1.0);
|
||||
return (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMultiSelectEnabled =
|
||||
ref.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
|
||||
return isMultiSelectEnabled
|
||||
? SliverToBoxAdapter(
|
||||
@@ -65,9 +61,7 @@ class _MesmerizingSliverAppBarState
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Platform.isIOS
|
||||
? Icons.arrow_back_ios_new_rounded
|
||||
: Icons.arrow_back,
|
||||
Platform.isIOS ? Icons.arrow_back_ios_new_rounded : Icons.arrow_back,
|
||||
color: Color.lerp(
|
||||
Colors.white,
|
||||
context.primaryColor,
|
||||
@@ -93,8 +87,7 @@ class _MesmerizingSliverAppBarState
|
||||
),
|
||||
flexibleSpace: Builder(
|
||||
builder: (context) {
|
||||
final settings = context.dependOnInheritedWidgetOfExactType<
|
||||
FlexibleSpaceBarSettings>();
|
||||
final settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
|
||||
final scrollProgress = _calculateScrollProgress(settings);
|
||||
|
||||
// Update scroll progress for the leading button
|
||||
@@ -145,12 +138,10 @@ class _ExpandedBackground extends ConsumerStatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<_ExpandedBackground> createState() =>
|
||||
_ExpandedBackgroundState();
|
||||
ConsumerState<_ExpandedBackground> createState() => _ExpandedBackgroundState();
|
||||
}
|
||||
|
||||
class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _slideController;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
|
||||
@@ -278,8 +269,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_reloadSubscription =
|
||||
EventStream.shared.listen<TimelineReloadEvent>((_) => setState(() {}));
|
||||
_reloadSubscription = EventStream.shared.listen<TimelineReloadEvent>((_) => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -328,8 +318,7 @@ class _RandomAssetBackground extends StatefulWidget {
|
||||
State<_RandomAssetBackground> createState() => _RandomAssetBackgroundState();
|
||||
}
|
||||
|
||||
class _RandomAssetBackgroundState extends State<_RandomAssetBackground>
|
||||
with TickerProviderStateMixin {
|
||||
class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with TickerProviderStateMixin {
|
||||
late AnimationController _zoomController;
|
||||
late AnimationController _crossFadeController;
|
||||
late Animation<double> _zoomAnimation;
|
||||
@@ -500,8 +489,7 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground>
|
||||
alignment: Alignment.topRight,
|
||||
image: getFullImageProvider(_currentAsset!),
|
||||
fit: BoxFit.cover,
|
||||
frameBuilder:
|
||||
(context, child, frame, wasSynchronouslyLoaded) {
|
||||
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
|
||||
if (wasSynchronouslyLoaded || frame != null) {
|
||||
return child;
|
||||
}
|
||||
@@ -532,8 +520,7 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground>
|
||||
alignment: Alignment.topRight,
|
||||
image: getFullImageProvider(_nextAsset!),
|
||||
fit: BoxFit.cover,
|
||||
frameBuilder:
|
||||
(context, child, frame, wasSynchronouslyLoaded) {
|
||||
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
|
||||
if (wasSynchronouslyLoaded || frame != null) {
|
||||
return child;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,10 @@ class RemoteAlbumSliverAppBar extends ConsumerStatefulWidget {
|
||||
final void Function()? onEditTitle;
|
||||
|
||||
@override
|
||||
ConsumerState<RemoteAlbumSliverAppBar> createState() =>
|
||||
_MesmerizingSliverAppBarState();
|
||||
ConsumerState<RemoteAlbumSliverAppBar> createState() => _MesmerizingSliverAppBarState();
|
||||
}
|
||||
|
||||
class _MesmerizingSliverAppBarState
|
||||
extends ConsumerState<RemoteAlbumSliverAppBar> {
|
||||
class _MesmerizingSliverAppBarState extends ConsumerState<RemoteAlbumSliverAppBar> {
|
||||
double _scrollProgress = 0.0;
|
||||
|
||||
double _calculateScrollProgress(FlexibleSpaceBarSettings? settings) {
|
||||
@@ -53,14 +51,12 @@ class _MesmerizingSliverAppBarState
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
return (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent)
|
||||
.clamp(0.0, 1.0);
|
||||
return (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMultiSelectEnabled =
|
||||
ref.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
|
||||
|
||||
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
||||
if (currentAlbum == null) {
|
||||
@@ -103,9 +99,7 @@ class _MesmerizingSliverAppBarState
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Platform.isIOS
|
||||
? Icons.arrow_back_ios_new_rounded
|
||||
: Icons.arrow_back,
|
||||
Platform.isIOS ? Icons.arrow_back_ios_new_rounded : Icons.arrow_back,
|
||||
color: actionIconColor,
|
||||
shadows: actionIconShadows,
|
||||
),
|
||||
@@ -136,8 +130,7 @@ class _MesmerizingSliverAppBarState
|
||||
],
|
||||
flexibleSpace: Builder(
|
||||
builder: (context) {
|
||||
final settings = context.dependOnInheritedWidgetOfExactType<
|
||||
FlexibleSpaceBarSettings>();
|
||||
final settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
|
||||
final scrollProgress = _calculateScrollProgress(settings);
|
||||
|
||||
// Update scroll progress for the leading button
|
||||
@@ -188,12 +181,10 @@ class _ExpandedBackground extends ConsumerStatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<_ExpandedBackground> createState() =>
|
||||
_ExpandedBackgroundState();
|
||||
ConsumerState<_ExpandedBackground> createState() => _ExpandedBackgroundState();
|
||||
}
|
||||
|
||||
class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _slideController;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
|
||||
@@ -405,8 +396,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_reloadSubscription =
|
||||
EventStream.shared.listen<TimelineReloadEvent>((_) => setState(() {}));
|
||||
_reloadSubscription = EventStream.shared.listen<TimelineReloadEvent>((_) => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -453,8 +443,7 @@ class _RandomAssetBackground extends StatefulWidget {
|
||||
State<_RandomAssetBackground> createState() => _RandomAssetBackgroundState();
|
||||
}
|
||||
|
||||
class _RandomAssetBackgroundState extends State<_RandomAssetBackground>
|
||||
with TickerProviderStateMixin {
|
||||
class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with TickerProviderStateMixin {
|
||||
late AnimationController _zoomController;
|
||||
late AnimationController _crossFadeController;
|
||||
late Animation<double> _zoomAnimation;
|
||||
@@ -625,8 +614,7 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground>
|
||||
alignment: Alignment.topRight,
|
||||
image: getFullImageProvider(_currentAsset!),
|
||||
fit: BoxFit.cover,
|
||||
frameBuilder:
|
||||
(context, child, frame, wasSynchronouslyLoaded) {
|
||||
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
|
||||
if (wasSynchronouslyLoaded || frame != null) {
|
||||
return child;
|
||||
}
|
||||
@@ -657,8 +645,7 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground>
|
||||
alignment: Alignment.topRight,
|
||||
image: getFullImageProvider(_nextAsset!),
|
||||
fit: BoxFit.cover,
|
||||
frameBuilder:
|
||||
(context, child, frame, wasSynchronouslyLoaded) {
|
||||
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
|
||||
if (wasSynchronouslyLoaded || frame != null) {
|
||||
return child;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,7 @@ class ScaffoldErrorBody extends StatelessWidget {
|
||||
child: Icon(
|
||||
Icons.error_outline,
|
||||
size: 100,
|
||||
color:
|
||||
context.themeData.iconTheme.color?.withValues(alpha: 0.5),
|
||||
color: context.themeData.iconTheme.color?.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -12,8 +12,7 @@ class SelectionSliverAppBar extends ConsumerStatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
ConsumerState<SelectionSliverAppBar> createState() =>
|
||||
_SelectionSliverAppBarState();
|
||||
ConsumerState<SelectionSliverAppBar> createState() => _SelectionSliverAppBarState();
|
||||
}
|
||||
|
||||
class _SelectionSliverAppBarState extends ConsumerState<SelectionSliverAppBar> {
|
||||
|
||||
@@ -13,8 +13,7 @@ OctoSet blurHashOrPlaceholder(
|
||||
}) {
|
||||
return OctoSet(
|
||||
placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit),
|
||||
errorBuilder:
|
||||
blurHashErrorBuilder(blurhash, fit: fit, message: errorMessage),
|
||||
errorBuilder: blurHashErrorBuilder(blurhash, fit: fit, message: errorMessage),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
|
||||
Widget userAvatar(BuildContext context, UserDto u, {double? radius}) {
|
||||
final url =
|
||||
"${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image";
|
||||
final url = "${Store.get(StoreKey.serverEndpoint)}/users/${u.id}/profile-image";
|
||||
final nameFirstLetter = u.name.isNotEmpty ? u.name[0] : "";
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
|
||||
@@ -34,9 +34,7 @@ class UserCircleAvatar extends ConsumerWidget {
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
color: userAvatarColor.computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
color: userAvatarColor.computeLuminance() > 0.5 ? Colors.black : Colors.white,
|
||||
),
|
||||
child: Text(user.name[0].toUpperCase()),
|
||||
);
|
||||
|
||||
@@ -17,10 +17,8 @@ class ChangePasswordForm extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final passwordController =
|
||||
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final confirmPasswordController =
|
||||
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final passwordController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final confirmPasswordController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final authState = ref.watch(authProvider);
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
@@ -72,20 +70,15 @@ class ChangePasswordForm extends HookConsumerWidget {
|
||||
passwordController: passwordController,
|
||||
onPressed: () async {
|
||||
if (formKey.currentState!.validate()) {
|
||||
var isSuccess = await ref
|
||||
.read(authProvider.notifier)
|
||||
.changePassword(passwordController.value.text);
|
||||
var isSuccess =
|
||||
await ref.read(authProvider.notifier).changePassword(passwordController.value.text);
|
||||
|
||||
if (isSuccess) {
|
||||
await ref.read(authProvider.notifier).logout();
|
||||
|
||||
ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.cancelBackup();
|
||||
ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||
ref.read(backupProvider.notifier).cancelBackup();
|
||||
await ref
|
||||
.read(assetProvider.notifier)
|
||||
.clearAllAssets();
|
||||
await ref.read(assetProvider.notifier).clearAllAssets();
|
||||
ref.read(websocketProvider.notifier).disconnect();
|
||||
|
||||
AutoRouter.of(context).back();
|
||||
|
||||
@@ -43,12 +43,9 @@ class LoginForm extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final emailController =
|
||||
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final passwordController =
|
||||
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final serverEndpointController =
|
||||
useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final emailController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final passwordController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final serverEndpointController = useTextEditingController.fromValue(TextEditingValue.empty);
|
||||
final emailFocusNode = useFocusNode();
|
||||
final passwordFocusNode = useFocusNode();
|
||||
final serverEndpointFocusNode = useFocusNode();
|
||||
@@ -102,8 +99,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
|
||||
try {
|
||||
isLoadingServer.value = true;
|
||||
final endpoint =
|
||||
await ref.read(authProvider.notifier).validateServerUrl(serverUrl);
|
||||
final endpoint = await ref.read(authProvider.notifier).validateServerUrl(serverUrl);
|
||||
|
||||
// Fetch and load server config and features
|
||||
await ref.read(serverInfoProvider.notifier).getServerInfo();
|
||||
@@ -114,9 +110,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
|
||||
isOauthEnable.value = features.oauthEnabled;
|
||||
isPasswordLoginEnable.value = features.passwordLogin;
|
||||
oAuthButtonLabel.value = config.oauthButtonText.isNotEmpty
|
||||
? config.oauthButtonText
|
||||
: 'OAuth';
|
||||
oAuthButtonLabel.value = config.oauthButtonText.isNotEmpty ? config.oauthButtonText : 'OAuth';
|
||||
|
||||
serverEndpoint.value = endpoint;
|
||||
} on ApiException catch (e) {
|
||||
@@ -196,9 +190,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
} else {
|
||||
final isBeta = Store.isBetaTimelineEnabled;
|
||||
if (isBeta) {
|
||||
await ref
|
||||
.read(galleryPermissionNotifier.notifier)
|
||||
.requestGalleryPermission();
|
||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||
await runNewSync(ref);
|
||||
context.replaceRoute(const TabShellRoute());
|
||||
return;
|
||||
@@ -218,8 +210,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
String generateRandomString(int length) {
|
||||
const chars =
|
||||
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
|
||||
const chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890';
|
||||
final random = Random.secure();
|
||||
return String.fromCharCodes(
|
||||
Iterable.generate(
|
||||
@@ -308,9 +299,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
ref.watch(backupProvider.notifier).resumeBackup();
|
||||
}
|
||||
if (isBeta) {
|
||||
await ref
|
||||
.read(galleryPermissionNotifier.notifier)
|
||||
.requestGalleryPermission();
|
||||
await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission();
|
||||
await runNewSync(ref);
|
||||
context.replaceRoute(const TabShellRoute());
|
||||
return;
|
||||
@@ -383,8 +372,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed:
|
||||
isLoadingServer.value ? null : getServerAuthSettings,
|
||||
onPressed: isLoadingServer.value ? null : getServerAuthSettings,
|
||||
icon: const Icon(Icons.arrow_forward_rounded),
|
||||
label: const Text(
|
||||
'next',
|
||||
@@ -412,14 +400,12 @@ class LoginForm extends HookConsumerWidget {
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
context.isDarkTheme ? Colors.red.shade700 : Colors.red.shade100,
|
||||
color: context.isDarkTheme ? Colors.red.shade700 : Colors.red.shade100,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
border: Border.all(
|
||||
color:
|
||||
context.isDarkTheme ? Colors.red.shade900 : Colors.red[200]!,
|
||||
color: context.isDarkTheme ? Colors.red.shade900 : Colors.red[200]!,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
@@ -465,8 +451,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 18),
|
||||
if (isPasswordLoginEnable.value)
|
||||
LoginButton(onPressed: login),
|
||||
if (isPasswordLoginEnable.value) LoginButton(onPressed: login),
|
||||
if (isOauthEnable.value) ...[
|
||||
if (isPasswordLoginEnable.value)
|
||||
Padding(
|
||||
@@ -474,9 +459,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
horizontal: 16.0,
|
||||
),
|
||||
child: Divider(
|
||||
color: context.isDarkTheme
|
||||
? Colors.white
|
||||
: Colors.black,
|
||||
color: context.isDarkTheme ? Colors.white : Colors.black,
|
||||
),
|
||||
),
|
||||
OAuthLoginButton(
|
||||
@@ -503,8 +486,7 @@ class LoginForm extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
final serverSelectionOrLogin =
|
||||
serverEndpoint.value == null ? buildSelectServer() : buildLogin();
|
||||
final serverSelectionOrLogin = serverEndpoint.value == null ? buildSelectServer() : buildLogin();
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
|
||||
@@ -33,9 +33,7 @@ class PasswordInput extends HookConsumerWidget {
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => isPasswordVisible.value = !isPasswordVisible.value,
|
||||
icon: Icon(
|
||||
isPasswordVisible.value
|
||||
? Icons.visibility_off_sharp
|
||||
: Icons.visibility_sharp,
|
||||
isPasswordVisible.value ? Icons.visibility_off_sharp : Icons.visibility_sharp,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -18,10 +18,7 @@ class ServerEndpointInput extends StatelessWidget {
|
||||
if (url == null || url.isEmpty) return null;
|
||||
|
||||
final parsedUrl = Uri.tryParse(sanitizeUrl(url));
|
||||
if (parsedUrl == null ||
|
||||
!parsedUrl.isAbsolute ||
|
||||
!parsedUrl.scheme.startsWith("http") ||
|
||||
parsedUrl.host.isEmpty) {
|
||||
if (parsedUrl == null || !parsedUrl.isAbsolute || !parsedUrl.scheme.startsWith("http") || parsedUrl.host.isEmpty) {
|
||||
return 'login_form_err_invalid_url'.tr();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,7 @@ class PinInput extends StatelessWidget {
|
||||
final minimumPadding = 18.0;
|
||||
final gapWidth = 3.0;
|
||||
final screenWidth = context.width;
|
||||
final pinWidth =
|
||||
(screenWidth - (minimumPadding * 2) - (gapWidth * 5)) / (length ?? 6);
|
||||
final pinWidth = (screenWidth - (minimumPadding * 2) - (gapWidth * 5)) / (length ?? 6);
|
||||
|
||||
if (pinWidth > 60) {
|
||||
return const Size(60, 64);
|
||||
@@ -62,8 +61,7 @@ class PinInput extends StatelessWidget {
|
||||
if (label != null) ...[
|
||||
Text(
|
||||
label!,
|
||||
style: context.textTheme.displayLarge
|
||||
?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)),
|
||||
style: context.textTheme.displayLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
|
||||
@@ -30,8 +30,7 @@ class PinVerificationForm extends HookConsumerWidget {
|
||||
final isVerified = useState(false);
|
||||
|
||||
verifyPin(String pinCode) async {
|
||||
final isUnlocked =
|
||||
await ref.read(authProvider.notifier).unlockPinCode(pinCode);
|
||||
final isUnlocked = await ref.read(authProvider.notifier).unlockPinCode(pinCode);
|
||||
|
||||
if (isUnlocked) {
|
||||
isVerified.value = true;
|
||||
@@ -58,9 +57,7 @@ class PinVerificationForm extends HookConsumerWidget {
|
||||
: Icon(
|
||||
icon ?? Icons.lock_outline_rounded,
|
||||
size: 64,
|
||||
color: hasError.value
|
||||
? context.colorScheme.error
|
||||
: context.primaryColor,
|
||||
color: hasError.value ? context.colorScheme.error : context.primaryColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 36),
|
||||
|
||||
@@ -22,9 +22,8 @@ class MapAppBar extends HookWidget implements PreferredSizeWidget {
|
||||
padding: EdgeInsets.only(top: context.padding.top + 25),
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: selectedAssets,
|
||||
builder: (ctx, value, child) => value.isNotEmpty
|
||||
? _SelectionRow(selectedAssets: selectedAssets)
|
||||
: const _NonSelectionRow(),
|
||||
builder: (ctx, value, child) =>
|
||||
value.isNotEmpty ? _SelectionRow(selectedAssets: selectedAssets) : const _NonSelectionRow(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@ class MapAssetGrid extends HookConsumerWidget {
|
||||
final cachedRenderList = useRef<RenderList?>(null);
|
||||
final lastRenderElementIndex = useRef<int?>(null);
|
||||
final assetInSheet = useValueNotifier<String?>(null);
|
||||
final gridScrollThrottler =
|
||||
useThrottler(interval: const Duration(milliseconds: 300));
|
||||
final gridScrollThrottler = useThrottler(interval: const Duration(milliseconds: 300));
|
||||
|
||||
// Add a cache for assets we've already loaded
|
||||
final assetCache = useRef<Map<String, Asset>>({});
|
||||
@@ -67,8 +66,7 @@ class MapAssetGrid extends HookConsumerWidget {
|
||||
|
||||
// Only fetch missing assets
|
||||
if (missingIds.isNotEmpty) {
|
||||
final newAssets =
|
||||
await ref.read(dbProvider).assets.getAllByRemoteId(missingIds);
|
||||
final newAssets = await ref.read(dbProvider).assets.getAllByRemoteId(missingIds);
|
||||
|
||||
// Add new assets to cache and current list
|
||||
for (final asset in newAssets) {
|
||||
@@ -93,8 +91,7 @@ class MapAssetGrid extends HookConsumerWidget {
|
||||
final orderedPos = positions.sortedByField((p) => p.index);
|
||||
// Index of row where the items are mostly visible
|
||||
const partialOffset = 0.20;
|
||||
final item = orderedPos
|
||||
.firstWhereOrNull((p) => p.itemTrailingEdge > partialOffset);
|
||||
final item = orderedPos.firstWhereOrNull((p) => p.itemTrailingEdge > partialOffset);
|
||||
|
||||
// Guard no elements, reset state
|
||||
// Also fail fast when the sheet is just opened and the user is yet to scroll (i.e leading = 0)
|
||||
@@ -103,8 +100,7 @@ class MapAssetGrid extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
final renderElement =
|
||||
cachedRenderList.value?.elements.elementAtOrNull(item.index);
|
||||
final renderElement = cachedRenderList.value?.elements.elementAtOrNull(item.index);
|
||||
// Guard no render list or render element
|
||||
if (renderElement == null) {
|
||||
return;
|
||||
@@ -128,13 +124,9 @@ class MapAssetGrid extends HookConsumerWidget {
|
||||
((renderElement.totalCount / assetsPerRow) * assetsPerRow).floor();
|
||||
|
||||
// trailing should never be above the totalOffset
|
||||
final columnOffset =
|
||||
(totalOffset - math.min(item.itemTrailingEdge, totalOffset)) ~/
|
||||
edgeOffset;
|
||||
final columnOffset = (totalOffset - math.min(item.itemTrailingEdge, totalOffset)) ~/ edgeOffset;
|
||||
final assetOffset = rowOffset + columnOffset;
|
||||
final selectedAsset = cachedRenderList.value?.allAssets
|
||||
?.elementAtOrNull(assetOffset)
|
||||
?.remoteId;
|
||||
final selectedAsset = cachedRenderList.value?.allAssets?.elementAtOrNull(assetOffset)?.remoteId;
|
||||
|
||||
if (selectedAsset != null) {
|
||||
onGridAssetChanged?.call(selectedAsset);
|
||||
@@ -154,9 +146,7 @@ class MapAssetGrid extends HookConsumerWidget {
|
||||
// Place it just below the drag handle
|
||||
heightFactor: 0.87,
|
||||
child: assetsInBounds.value.isNotEmpty
|
||||
? ref
|
||||
.watch(assetsTimelineProvider(assetsInBounds.value))
|
||||
.when(
|
||||
? ref.watch(assetsTimelineProvider(assetsInBounds.value)).when(
|
||||
data: (renderList) {
|
||||
// Cache render list here to use it back during visibleItemsListener
|
||||
cachedRenderList.value = renderList;
|
||||
@@ -170,8 +160,7 @@ class MapAssetGrid extends HookConsumerWidget {
|
||||
showMultiSelectIndicator: false,
|
||||
selectionActive: value.isNotEmpty,
|
||||
listener: onAssetsSelected,
|
||||
visibleItemsListener: (pos) => gridScrollThrottler
|
||||
.run(() => handleVisibleItems(pos)),
|
||||
visibleItemsListener: (pos) => gridScrollThrottler.run(() => handleVisibleItems(pos)),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -255,8 +244,7 @@ class _MapSheetDragRegion extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final assetsInBoundsText = assetsInBoundCount > 0
|
||||
? "map_assets_in_bounds"
|
||||
.tr(namedArgs: {'count': assetsInBoundCount.toString()})
|
||||
? "map_assets_in_bounds".tr(namedArgs: {'count': assetsInBoundCount.toString()})
|
||||
: "map_no_assets_in_bounds".tr();
|
||||
|
||||
return SingleChildScrollView(
|
||||
@@ -287,8 +275,7 @@ class _MapSheetDragRegion extends StatelessWidget {
|
||||
assetsInBoundsText,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: context.textTheme.displayLarge?.color
|
||||
?.withValues(alpha: 0.75),
|
||||
color: context.textTheme.displayLarge?.color?.withValues(alpha: 0.75),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -45,8 +45,7 @@ class MapBottomSheet extends HookConsumerWidget {
|
||||
useOnStreamChange<MapEvent>(mapEventStream, onData: handleMapEvents);
|
||||
|
||||
bool onScrollNotification(DraggableScrollableNotification notification) {
|
||||
isBottomSheetOpened.value =
|
||||
notification.extent > (notification.maxExtent * 0.9);
|
||||
isBottomSheetOpened.value = notification.extent > (notification.maxExtent * 0.9);
|
||||
bottomSheetOffset.value = notification.extent;
|
||||
// do not bubble
|
||||
return true;
|
||||
@@ -70,9 +69,7 @@ class MapBottomSheet extends HookConsumerWidget {
|
||||
selectedAssets: selectedAssets,
|
||||
onAssetsSelected: onAssetsSelected,
|
||||
// Do not bother with the event if the bottom sheet is not user scrolled
|
||||
onGridAssetChanged: (assetId) => isBottomSheetOpened.value
|
||||
? onGridAssetChanged?.call(assetId)
|
||||
: null,
|
||||
onGridAssetChanged: (assetId) => isBottomSheetOpened.value ? onGridAssetChanged?.call(assetId) : null,
|
||||
onZoomToAsset: onZoomToAsset,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -21,8 +21,7 @@ class MapSettingsListTile extends StatelessWidget {
|
||||
activeColor: context.primaryColor,
|
||||
title: Text(
|
||||
title,
|
||||
style:
|
||||
context.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
style: context.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
value: selected,
|
||||
onChanged: onChanged,
|
||||
|
||||
@@ -81,8 +81,7 @@ class MapTimeDropDown extends StatelessWidget {
|
||||
),
|
||||
)
|
||||
.inDays,
|
||||
label: "map_settings_date_range_option_years"
|
||||
.tr(namedArgs: {'years': "3"}),
|
||||
label: "map_settings_date_range_option_years".tr(namedArgs: {'years': "3"}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -23,8 +23,7 @@ class MapThemePicker extends StatelessWidget {
|
||||
child: Center(
|
||||
child: Text(
|
||||
"map_settings_theme_settings",
|
||||
style: context.textTheme.bodyMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
),
|
||||
@@ -79,9 +78,7 @@ class _BorderedMapThumbnail extends StatelessWidget {
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(
|
||||
width: 4,
|
||||
color: shouldHighlight
|
||||
? context.colorScheme.onSurface
|
||||
: Colors.transparent,
|
||||
color: shouldHighlight ? context.colorScheme.onSurface : Colors.transparent,
|
||||
),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
|
||||
@@ -26,37 +26,30 @@ class MapSettingsSheet extends HookConsumerWidget {
|
||||
children: [
|
||||
MapThemePicker(
|
||||
themeMode: mapState.themeMode,
|
||||
onThemeChange: (mode) => ref
|
||||
.read(mapStateNotifierProvider.notifier)
|
||||
.switchTheme(mode),
|
||||
onThemeChange: (mode) => ref.read(mapStateNotifierProvider.notifier).switchTheme(mode),
|
||||
),
|
||||
const Divider(height: 30, thickness: 2),
|
||||
MapSettingsListTile(
|
||||
title: "map_settings_only_show_favorites",
|
||||
selected: mapState.showFavoriteOnly,
|
||||
onChanged: (favoriteOnly) => ref
|
||||
.read(mapStateNotifierProvider.notifier)
|
||||
.switchFavoriteOnly(favoriteOnly),
|
||||
onChanged: (favoriteOnly) =>
|
||||
ref.read(mapStateNotifierProvider.notifier).switchFavoriteOnly(favoriteOnly),
|
||||
),
|
||||
MapSettingsListTile(
|
||||
title: "map_settings_include_show_archived",
|
||||
selected: mapState.includeArchived,
|
||||
onChanged: (includeArchive) => ref
|
||||
.read(mapStateNotifierProvider.notifier)
|
||||
.switchIncludeArchived(includeArchive),
|
||||
onChanged: (includeArchive) =>
|
||||
ref.read(mapStateNotifierProvider.notifier).switchIncludeArchived(includeArchive),
|
||||
),
|
||||
MapSettingsListTile(
|
||||
title: "map_settings_include_show_partners",
|
||||
selected: mapState.withPartners,
|
||||
onChanged: (withPartners) => ref
|
||||
.read(mapStateNotifierProvider.notifier)
|
||||
.switchWithPartners(withPartners),
|
||||
onChanged: (withPartners) =>
|
||||
ref.read(mapStateNotifierProvider.notifier).switchWithPartners(withPartners),
|
||||
),
|
||||
MapTimeDropDown(
|
||||
relativeTime: mapState.relativeTime,
|
||||
onTimeChange: (time) => ref
|
||||
.read(mapStateNotifierProvider.notifier)
|
||||
.setRelativeTime(time),
|
||||
onTimeChange: (time) => ref.read(mapStateNotifierProvider.notifier).setRelativeTime(time),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
|
||||
@@ -18,25 +18,20 @@ class MapThemeOverride extends StatefulHookConsumerWidget {
|
||||
ConsumerState createState() => _MapThemeOverrideState();
|
||||
}
|
||||
|
||||
class _MapThemeOverrideState extends ConsumerState<MapThemeOverride>
|
||||
with WidgetsBindingObserver {
|
||||
class _MapThemeOverrideState extends ConsumerState<MapThemeOverride> with WidgetsBindingObserver {
|
||||
late ThemeMode _theme;
|
||||
bool _isDarkTheme = false;
|
||||
|
||||
bool get _isSystemDark =>
|
||||
WidgetsBinding.instance.platformDispatcher.platformBrightness ==
|
||||
Brightness.dark;
|
||||
bool get _isSystemDark => WidgetsBinding.instance.platformDispatcher.platformBrightness == Brightness.dark;
|
||||
|
||||
bool checkDarkTheme() {
|
||||
return _theme == ThemeMode.dark ||
|
||||
_theme == ThemeMode.system && _isSystemDark;
|
||||
return _theme == ThemeMode.dark || _theme == ThemeMode.system && _isSystemDark;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_theme = widget.themeMode ??
|
||||
ref.read(mapStateNotifierProvider.select((v) => v.themeMode));
|
||||
_theme = widget.themeMode ?? ref.read(mapStateNotifierProvider.select((v) => v.themeMode));
|
||||
setState(() {
|
||||
_isDarkTheme = checkDarkTheme();
|
||||
});
|
||||
@@ -70,8 +65,7 @@ class _MapThemeOverrideState extends ConsumerState<MapThemeOverride>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_theme = widget.themeMode ??
|
||||
ref.watch(mapStateNotifierProvider.select((v) => v.themeMode));
|
||||
_theme = widget.themeMode ?? ref.watch(mapStateNotifierProvider.select((v) => v.themeMode));
|
||||
var appTheme = ref.watch(immichThemeProvider);
|
||||
final locale = ref.watch(localeProvider);
|
||||
|
||||
|
||||
@@ -55,8 +55,7 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
// The iOS impl returns wrong toScreenLocation without the delay
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
() async =>
|
||||
position.value = await mapController.toScreenLocation(centre),
|
||||
() async => position.value = await mapController.toScreenLocation(centre),
|
||||
);
|
||||
}
|
||||
onCreated?.call(mapController);
|
||||
@@ -81,8 +80,7 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
duration: Durations.medium2,
|
||||
curve: Curves.easeOut,
|
||||
foregroundDecoration: BoxDecoration(
|
||||
color: context.colorScheme.inverseSurface
|
||||
.withAlpha(styleLoaded.value ? 0 : 200),
|
||||
color: context.colorScheme.inverseSurface.withAlpha(styleLoaded.value ? 0 : 200),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||
),
|
||||
height: height,
|
||||
@@ -94,8 +92,7 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
children: [
|
||||
style.widgetWhen(
|
||||
onData: (style) => MapLibreMap(
|
||||
initialCameraPosition:
|
||||
CameraPosition(target: offsettedCentre, zoom: zoom),
|
||||
initialCameraPosition: CameraPosition(target: offsettedCentre, zoom: zoom),
|
||||
styleString: style,
|
||||
onMapCreated: onMapCreated,
|
||||
onStyleLoadedCallback: onStyleLoaded,
|
||||
@@ -107,20 +104,18 @@ class MapThumbnail extends HookConsumerWidget {
|
||||
scrollGesturesEnabled: false,
|
||||
rotateGesturesEnabled: false,
|
||||
myLocationEnabled: false,
|
||||
attributionButtonMargins:
|
||||
showAttribution == false ? const Point(-100, 0) : null,
|
||||
attributionButtonMargins: showAttribution == false ? const Point(-100, 0) : null,
|
||||
),
|
||||
),
|
||||
ValueListenableBuilder(
|
||||
valueListenable: position,
|
||||
builder: (_, value, __) =>
|
||||
value != null && assetMarkerRemoteId != null
|
||||
? PositionedAssetMarkerIcon(
|
||||
size: height / 2,
|
||||
point: value,
|
||||
assetRemoteId: assetMarkerRemoteId!,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
builder: (_, value, __) => value != null && assetMarkerRemoteId != null
|
||||
? PositionedAssetMarkerIcon(
|
||||
size: height / 2,
|
||||
point: value,
|
||||
assetRemoteId: assetMarkerRemoteId!,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -89,8 +89,7 @@ class _AssetMarkerIcon extends StatelessWidget {
|
||||
imageUrl,
|
||||
cacheKey: cacheKey,
|
||||
headers: ApiService.getRequestHeaders(),
|
||||
errorListener: (_) =>
|
||||
const Icon(Icons.image_not_supported_outlined),
|
||||
errorListener: (_) => const Icon(Icons.image_not_supported_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -175,7 +174,6 @@ class _PinPainter extends CustomPainter {
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_PinPainter old) {
|
||||
return old.primaryColor != primaryColor ||
|
||||
old.secondaryColor != secondaryColor;
|
||||
return old.primaryColor != primaryColor || old.secondaryColor != secondaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,7 @@ class MemoryBottomInfo extends StatelessWidget {
|
||||
minWidth: 0,
|
||||
onPressed: () {
|
||||
context.maybePop();
|
||||
scrollToDateNotifierProvider
|
||||
.scrollToDate(memory.assets[0].fileCreatedAt);
|
||||
scrollToDateNotifierProvider.scrollToDate(memory.assets[0].fileCreatedAt);
|
||||
},
|
||||
shape: const CircleBorder(),
|
||||
color: Colors.white.withValues(alpha: 0.2),
|
||||
|
||||
@@ -45,11 +45,9 @@ class MemoryCard extends StatelessWidget {
|
||||
BoxFit fit = BoxFit.contain;
|
||||
if (asset.width != null && asset.height != null) {
|
||||
final aspectRatio = asset.width! / asset.height!;
|
||||
final phoneAspectRatio =
|
||||
constraints.maxWidth / constraints.maxHeight;
|
||||
final phoneAspectRatio = constraints.maxWidth / constraints.maxHeight;
|
||||
// Look for a 25% difference in either direction
|
||||
if (phoneAspectRatio * .75 < aspectRatio &&
|
||||
phoneAspectRatio * 1.25 > aspectRatio) {
|
||||
if (phoneAspectRatio * .75 < aspectRatio && phoneAspectRatio * 1.25 > aspectRatio) {
|
||||
// Cover to look nice if we have nearly the same aspect ratio
|
||||
fit = BoxFit.cover;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ class MemoryEpilogue extends StatefulWidget {
|
||||
State<MemoryEpilogue> createState() => _MemoryEpilogueState();
|
||||
}
|
||||
|
||||
class _MemoryEpilogueState extends State<MemoryEpilogue>
|
||||
with TickerProviderStateMixin {
|
||||
class _MemoryEpilogueState extends State<MemoryEpilogue> with TickerProviderStateMixin {
|
||||
late final _animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(
|
||||
@@ -50,9 +49,7 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle_outline_sharp,
|
||||
color: context.isDarkTheme
|
||||
? context.colorScheme.primary
|
||||
: context.colorScheme.inversePrimary,
|
||||
color: context.isDarkTheme ? context.colorScheme.primary : context.colorScheme.inversePrimary,
|
||||
size: 64.0,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
@@ -75,9 +72,7 @@ class _MemoryEpilogueState extends State<MemoryEpilogue>
|
||||
child: Text(
|
||||
"memories_start_over",
|
||||
style: context.textTheme.displayMedium?.copyWith(
|
||||
color: context.isDarkTheme
|
||||
? context.colorScheme.primary
|
||||
: context.colorScheme.inversePrimary,
|
||||
color: context.isDarkTheme ? context.colorScheme.primary : context.colorScheme.inversePrimary,
|
||||
),
|
||||
).tr(),
|
||||
),
|
||||
|
||||
@@ -27,9 +27,7 @@ class MemoryProgressIndicator extends StatelessWidget {
|
||||
value: value,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
backgroundColor: Colors.grey[800],
|
||||
color: context.isDarkTheme
|
||||
? context.colorScheme.primary
|
||||
: context.colorScheme.inversePrimary,
|
||||
color: context.isDarkTheme ? context.colorScheme.primary : context.colorScheme.inversePrimary,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
|
||||
@@ -9,8 +9,7 @@ import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attri
|
||||
|
||||
export 'src/controller/photo_view_controller.dart';
|
||||
export 'src/controller/photo_view_scalestate_controller.dart';
|
||||
export 'src/core/photo_view_gesture_detector.dart'
|
||||
show PhotoViewGestureDetectorScope, PhotoViewPageViewScrollPhysics;
|
||||
export 'src/core/photo_view_gesture_detector.dart' show PhotoViewGestureDetectorScope, PhotoViewPageViewScrollPhysics;
|
||||
export 'src/photo_view_computed_scale.dart';
|
||||
export 'src/photo_view_scale_state.dart';
|
||||
export 'src/utils/photo_view_hero_attributes.dart';
|
||||
@@ -461,8 +460,7 @@ class PhotoView extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _PhotoViewState extends State<PhotoView>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
class _PhotoViewState extends State<PhotoView> with AutomaticKeepAliveClientMixin {
|
||||
// image retrieval
|
||||
|
||||
// controller
|
||||
@@ -550,8 +548,7 @@ class _PhotoViewState extends State<PhotoView>
|
||||
BoxConstraints constraints,
|
||||
) {
|
||||
final computedOuterSize = widget.customSize ?? constraints.biggest;
|
||||
final backgroundDecoration = widget.backgroundDecoration ??
|
||||
const BoxDecoration(color: Colors.black);
|
||||
final backgroundDecoration = widget.backgroundDecoration ?? const BoxDecoration(color: Colors.black);
|
||||
|
||||
return widget._isCustomChild
|
||||
? CustomChildWrapper(
|
||||
@@ -625,14 +622,11 @@ class _PhotoViewState extends State<PhotoView>
|
||||
}
|
||||
|
||||
/// The default [ScaleStateCycle]
|
||||
PhotoViewScaleState defaultScaleStateCycle(PhotoViewScaleState actual) =>
|
||||
switch (actual) {
|
||||
PhotoViewScaleState defaultScaleStateCycle(PhotoViewScaleState actual) => switch (actual) {
|
||||
PhotoViewScaleState.initial => PhotoViewScaleState.covering,
|
||||
PhotoViewScaleState.covering => PhotoViewScaleState.originalSize,
|
||||
PhotoViewScaleState.originalSize => PhotoViewScaleState.initial,
|
||||
PhotoViewScaleState.zoomedIn ||
|
||||
PhotoViewScaleState.zoomedOut =>
|
||||
PhotoViewScaleState.initial,
|
||||
PhotoViewScaleState.zoomedIn || PhotoViewScaleState.zoomedOut => PhotoViewScaleState.initial,
|
||||
};
|
||||
|
||||
/// A type definition for a [Function] that receives the actual [PhotoViewScaleState] and returns the next one
|
||||
|
||||
@@ -218,8 +218,7 @@ class PhotoViewGallery extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PhotoViewGalleryState extends State<PhotoViewGallery> {
|
||||
late final PageController _controller =
|
||||
widget.pageController ?? PageController();
|
||||
late final PageController _controller = widget.pageController ?? PageController();
|
||||
PhotoViewControllerCallback? _getController;
|
||||
|
||||
void scaleStateChangedCallback(PhotoViewScaleState scaleState) {
|
||||
|
||||
@@ -106,11 +106,7 @@ class PhotoViewControllerValue {
|
||||
rotationFocusPoint == other.rotationFocusPoint;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
position.hashCode ^
|
||||
scale.hashCode ^
|
||||
rotation.hashCode ^
|
||||
rotationFocusPoint.hashCode;
|
||||
int get hashCode => position.hashCode ^ scale.hashCode ^ rotation.hashCode ^ rotationFocusPoint.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@@ -125,8 +121,7 @@ class PhotoViewControllerValue {
|
||||
///
|
||||
/// For details of fields and methods, check [PhotoViewControllerBase].
|
||||
///
|
||||
class PhotoViewController
|
||||
implements PhotoViewControllerBase<PhotoViewControllerValue> {
|
||||
class PhotoViewController implements PhotoViewControllerBase<PhotoViewControllerValue> {
|
||||
PhotoViewController({
|
||||
Offset initialPosition = Offset.zero,
|
||||
double initialRotation = 0.0,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart'
|
||||
show
|
||||
PhotoViewControllerBase,
|
||||
PhotoViewScaleState,
|
||||
PhotoViewScaleStateController,
|
||||
ScaleStateCycle;
|
||||
show PhotoViewControllerBase, PhotoViewScaleState, PhotoViewScaleStateController, ScaleStateCycle;
|
||||
import 'package:immich_mobile/widgets/photo_view/src/core/photo_view_core.dart';
|
||||
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_utils.dart';
|
||||
|
||||
@@ -14,8 +10,7 @@ import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_utils.dart
|
||||
mixin PhotoViewControllerDelegate on State<PhotoViewCore> {
|
||||
PhotoViewControllerBase get controller => widget.controller;
|
||||
|
||||
PhotoViewScaleStateController get scaleStateController =>
|
||||
widget.scaleStateController;
|
||||
PhotoViewScaleStateController get scaleStateController => widget.scaleStateController;
|
||||
|
||||
ScaleBoundaries get scaleBoundaries => widget.scaleBoundaries;
|
||||
|
||||
@@ -68,9 +63,7 @@ mixin PhotoViewControllerDelegate on State<PhotoViewCore> {
|
||||
return;
|
||||
}
|
||||
final PhotoViewScaleState newScaleState =
|
||||
(scale > scaleBoundaries.initialScale)
|
||||
? PhotoViewScaleState.zoomedIn
|
||||
: PhotoViewScaleState.zoomedOut;
|
||||
(scale > scaleBoundaries.initialScale) ? PhotoViewScaleState.zoomedIn : PhotoViewScaleState.zoomedOut;
|
||||
|
||||
scaleStateController.setInvisibly(newScaleState);
|
||||
}
|
||||
@@ -79,8 +72,7 @@ mixin PhotoViewControllerDelegate on State<PhotoViewCore> {
|
||||
|
||||
double get scale {
|
||||
// for figuring out initial scale
|
||||
final needsRecalc = markNeedsScaleRecalc &&
|
||||
!scaleStateController.scaleState.isScaleStateZooming;
|
||||
final needsRecalc = markNeedsScaleRecalc && !scaleStateController.scaleState.isScaleStateZooming;
|
||||
|
||||
final scaleExistsOnController = controller.scale != null;
|
||||
if (needsRecalc || !scaleExistsOnController) {
|
||||
@@ -114,9 +106,8 @@ mixin PhotoViewControllerDelegate on State<PhotoViewCore> {
|
||||
PhotoViewScaleState getScaleStateFromNewScale(double newScale) {
|
||||
PhotoViewScaleState newScaleState = PhotoViewScaleState.initial;
|
||||
if (scale != scaleBoundaries.initialScale) {
|
||||
newScaleState = (newScale > scaleBoundaries.initialScale)
|
||||
? PhotoViewScaleState.zoomedIn
|
||||
: PhotoViewScaleState.zoomedOut;
|
||||
newScaleState =
|
||||
(newScale > scaleBoundaries.initialScale) ? PhotoViewScaleState.zoomedIn : PhotoViewScaleState.zoomedOut;
|
||||
}
|
||||
return newScaleState;
|
||||
}
|
||||
@@ -124,17 +115,15 @@ mixin PhotoViewControllerDelegate on State<PhotoViewCore> {
|
||||
void updateScaleStateFromNewScale(double newScale) {
|
||||
PhotoViewScaleState newScaleState = PhotoViewScaleState.initial;
|
||||
if (scale != scaleBoundaries.initialScale) {
|
||||
newScaleState = (newScale > scaleBoundaries.initialScale)
|
||||
? PhotoViewScaleState.zoomedIn
|
||||
: PhotoViewScaleState.zoomedOut;
|
||||
newScaleState =
|
||||
(newScale > scaleBoundaries.initialScale) ? PhotoViewScaleState.zoomedIn : PhotoViewScaleState.zoomedOut;
|
||||
}
|
||||
scaleStateController.setInvisibly(newScaleState);
|
||||
}
|
||||
|
||||
void nextScaleState() {
|
||||
final PhotoViewScaleState scaleState = scaleStateController.scaleState;
|
||||
if (scaleState == PhotoViewScaleState.zoomedIn ||
|
||||
scaleState == PhotoViewScaleState.zoomedOut) {
|
||||
if (scaleState == PhotoViewScaleState.zoomedIn || scaleState == PhotoViewScaleState.zoomedOut) {
|
||||
scaleStateController.scaleState = scaleStateCycle(scaleState);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,17 +20,14 @@ typedef ScaleStateListener = void Function(double prevScale, double nextScale);
|
||||
///
|
||||
class PhotoViewScaleStateController {
|
||||
late final IgnorableValueNotifier<PhotoViewScaleState> _scaleStateNotifier =
|
||||
IgnorableValueNotifier(PhotoViewScaleState.initial)
|
||||
..addListener(_scaleStateChangeListener);
|
||||
final StreamController<PhotoViewScaleState> _outputScaleStateCtrl =
|
||||
StreamController<PhotoViewScaleState>.broadcast()
|
||||
..sink.add(PhotoViewScaleState.initial);
|
||||
IgnorableValueNotifier(PhotoViewScaleState.initial)..addListener(_scaleStateChangeListener);
|
||||
final StreamController<PhotoViewScaleState> _outputScaleStateCtrl = StreamController<PhotoViewScaleState>.broadcast()
|
||||
..sink.add(PhotoViewScaleState.initial);
|
||||
|
||||
bool _hasZoomedOutManually = false;
|
||||
|
||||
/// The output for state/value updates
|
||||
Stream<PhotoViewScaleState> get outputScaleStateStream =>
|
||||
_outputScaleStateCtrl.stream;
|
||||
Stream<PhotoViewScaleState> get outputScaleStateStream => _outputScaleStateCtrl.stream;
|
||||
|
||||
/// The state value before the last change or the initial state if the state has not been changed.
|
||||
PhotoViewScaleState prevScaleState = PhotoViewScaleState.initial;
|
||||
@@ -62,9 +59,7 @@ class PhotoViewScaleStateController {
|
||||
bool get hasChanged => prevScaleState != scaleState;
|
||||
|
||||
/// Check if is `zoomedIn` & `zoomedOut`
|
||||
bool get isZooming =>
|
||||
scaleState == PhotoViewScaleState.zoomedIn ||
|
||||
scaleState == PhotoViewScaleState.zoomedOut;
|
||||
bool get isZooming => scaleState == PhotoViewScaleState.zoomedIn || scaleState == PhotoViewScaleState.zoomedOut;
|
||||
|
||||
/// Resets the state to the initial value;
|
||||
void reset() {
|
||||
|
||||
@@ -122,10 +122,7 @@ class PhotoViewCore extends StatefulWidget {
|
||||
}
|
||||
|
||||
class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
with
|
||||
TickerProviderStateMixin,
|
||||
PhotoViewControllerDelegate,
|
||||
HitCornersDetector {
|
||||
with TickerProviderStateMixin, PhotoViewControllerDelegate, HitCornersDetector {
|
||||
Offset? _normalizedPosition;
|
||||
double? _scaleBefore;
|
||||
double? _rotationBefore;
|
||||
@@ -136,8 +133,8 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
late final AnimationController _positionAnimationController;
|
||||
Animation<Offset>? _positionAnimation;
|
||||
|
||||
late final AnimationController _rotationAnimationController =
|
||||
AnimationController(vsync: this)..addListener(handleRotationAnimation);
|
||||
late final AnimationController _rotationAnimationController = AnimationController(vsync: this)
|
||||
..addListener(handleRotationAnimation);
|
||||
Animation<double>? _rotationAnimation;
|
||||
|
||||
PhotoViewHeroAttributes? get heroAttributes => widget.heroAttributes;
|
||||
@@ -166,8 +163,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
}
|
||||
|
||||
bool _shouldAllowPanRotate() => switch (scaleStateController.scaleState) {
|
||||
PhotoViewScaleState.zoomedIn =>
|
||||
scaleStateController.hasZoomedOutManually,
|
||||
PhotoViewScaleState.zoomedIn => scaleStateController.hasZoomedOutManually,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
@@ -182,8 +178,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
|
||||
updateMultiple(
|
||||
scale: newScale,
|
||||
position:
|
||||
panEnabled ? delta : clampPosition(position: delta * details.scale),
|
||||
position: panEnabled ? delta : clampPosition(position: delta * details.scale),
|
||||
rotation: rotationEnabled ? _rotationBefore! + details.rotation : null,
|
||||
rotationFocusPoint: rotationEnabled ? details.focalPoint : null,
|
||||
);
|
||||
@@ -266,8 +261,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_positionAnimation = Tween<Offset>(begin: from, end: to)
|
||||
.animate(_positionAnimationController);
|
||||
_positionAnimation = Tween<Offset>(begin: from, end: to).animate(_positionAnimationController);
|
||||
_positionAnimationController
|
||||
..value = 0.0
|
||||
..fling(velocity: 0.4);
|
||||
@@ -277,8 +271,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_rotationAnimation = Tween<double>(begin: from, end: to)
|
||||
.animate(_rotationAnimationController);
|
||||
_rotationAnimation = Tween<double>(begin: from, end: to).animate(_rotationAnimationController);
|
||||
_rotationAnimationController
|
||||
..value = 0.0
|
||||
..fling(velocity: 0.4);
|
||||
@@ -292,8 +285,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
|
||||
/// Check if scale is equal to initial after scale animation update
|
||||
void onAnimationStatusCompleted() {
|
||||
if (scaleStateController.scaleState != PhotoViewScaleState.initial &&
|
||||
scale == scaleBoundaries.initialScale) {
|
||||
if (scaleStateController.scaleState != PhotoViewScaleState.initial && scale == scaleBoundaries.initialScale) {
|
||||
scaleStateController.setInvisibly(PhotoViewScaleState.initial);
|
||||
}
|
||||
}
|
||||
@@ -326,8 +318,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
_scaleAnimationController = AnimationController(vsync: this)
|
||||
..addListener(handleScaleAnimation)
|
||||
..addStatusListener(onAnimationStatus);
|
||||
_positionAnimationController = AnimationController(vsync: this)
|
||||
..addListener(handlePositionAnimate);
|
||||
_positionAnimationController = AnimationController(vsync: this)..addListener(handlePositionAnimate);
|
||||
}
|
||||
|
||||
void animateOnScaleStateUpdate(double prevScale, double nextScale) {
|
||||
@@ -389,9 +380,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
);
|
||||
|
||||
final child = Container(
|
||||
constraints: widget.tightMode
|
||||
? BoxConstraints.tight(scaleBoundaries.childSize * scale)
|
||||
: null,
|
||||
constraints: widget.tightMode ? BoxConstraints.tight(scaleBoundaries.childSize * scale) : null,
|
||||
decoration: widget.backgroundDecoration ?? _defaultDecoration,
|
||||
child: Center(
|
||||
child: Transform(
|
||||
@@ -421,8 +410,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
)
|
||||
: null,
|
||||
onDragEnd: widget.onDragEnd != null
|
||||
? (details) =>
|
||||
widget.onDragEnd!(context, details, widget.controller.value)
|
||||
? (details) => widget.onDragEnd!(context, details, widget.controller.value)
|
||||
: null,
|
||||
onDragUpdate: widget.onDragUpdate != null
|
||||
? (details) => widget.onDragUpdate!(
|
||||
@@ -432,15 +420,10 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
)
|
||||
: null,
|
||||
hitDetector: this,
|
||||
onTapUp: widget.onTapUp != null
|
||||
? (details) => widget.onTapUp!(context, details, value)
|
||||
: null,
|
||||
onTapDown: widget.onTapDown != null
|
||||
? (details) => widget.onTapDown!(context, details, value)
|
||||
: null,
|
||||
onLongPressStart: widget.onLongPressStart != null
|
||||
? (details) => widget.onLongPressStart!(context, details, value)
|
||||
: null,
|
||||
onTapUp: widget.onTapUp != null ? (details) => widget.onTapUp!(context, details, value) : null,
|
||||
onTapDown: widget.onTapDown != null ? (details) => widget.onTapDown!(context, details, value) : null,
|
||||
onLongPressStart:
|
||||
widget.onLongPressStart != null ? (details) => widget.onLongPressStart!(context, details, value) : null,
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
@@ -467,9 +450,7 @@ class PhotoViewCoreState extends State<PhotoViewCore>
|
||||
return widget.hasCustomChild
|
||||
? widget.customChild!
|
||||
: Image(
|
||||
key: widget.heroAttributes?.tag != null
|
||||
? ObjectKey(widget.heroAttributes!.tag)
|
||||
: null,
|
||||
key: widget.heroAttributes?.tag != null ? ObjectKey(widget.heroAttributes!.tag) : null,
|
||||
image: widget.imageProvider!,
|
||||
semanticLabel: widget.semanticLabel,
|
||||
gaplessPlayback: widget.gaplessPlayback ?? false,
|
||||
@@ -507,9 +488,7 @@ class _CenterWithOriginalSizeDelegate extends SingleChildLayoutDelegate {
|
||||
|
||||
@override
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
return useImageScale
|
||||
? const BoxConstraints()
|
||||
: BoxConstraints.tight(subjectSize);
|
||||
return useImageScale ? const BoxConstraints() : BoxConstraints.tight(subjectSize);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -527,6 +506,5 @@ class _CenterWithOriginalSizeDelegate extends SingleChildLayoutDelegate {
|
||||
useImageScale == other.useImageScale;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
subjectSize.hashCode ^ basePosition.hashCode ^ useImageScale.hashCode;
|
||||
int get hashCode => subjectSize.hashCode ^ basePosition.hashCode ^ useImageScale.hashCode;
|
||||
}
|
||||
|
||||
@@ -53,12 +53,10 @@ class PhotoViewGestureDetector extends StatelessWidget {
|
||||
final Axis? axis = scope?.axis;
|
||||
final touchSlopFactor = scope?.touchSlopFactor ?? 2;
|
||||
|
||||
final Map<Type, GestureRecognizerFactory> gestures =
|
||||
<Type, GestureRecognizerFactory>{};
|
||||
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
|
||||
|
||||
if (onTapDown != null || onTapUp != null) {
|
||||
gestures[TapGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
() => TapGestureRecognizer(debugOwner: this),
|
||||
(TapGestureRecognizer instance) {
|
||||
instance
|
||||
@@ -69,8 +67,7 @@ class PhotoViewGestureDetector extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (onDragStart != null || onDragEnd != null || onDragUpdate != null) {
|
||||
gestures[VerticalDragGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
|
||||
gestures[VerticalDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
|
||||
() => VerticalDragGestureRecognizer(debugOwner: this),
|
||||
(VerticalDragGestureRecognizer instance) {
|
||||
instance
|
||||
@@ -81,16 +78,14 @@ class PhotoViewGestureDetector extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
gestures[DoubleTapGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
||||
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
||||
() => DoubleTapGestureRecognizer(debugOwner: this),
|
||||
(DoubleTapGestureRecognizer instance) {
|
||||
instance.onDoubleTap = onDoubleTap;
|
||||
},
|
||||
);
|
||||
|
||||
gestures[PhotoViewGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<PhotoViewGestureRecognizer>(
|
||||
gestures[PhotoViewGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PhotoViewGestureRecognizer>(
|
||||
() => PhotoViewGestureRecognizer(
|
||||
hitDetector: hitDetector,
|
||||
debugOwner: this,
|
||||
@@ -107,10 +102,8 @@ class PhotoViewGestureDetector extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
|
||||
gestures[LongPressGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
||||
() => LongPressGestureRecognizer(debugOwner: this),
|
||||
(LongPressGestureRecognizer instance) {
|
||||
gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
||||
() => LongPressGestureRecognizer(debugOwner: this), (LongPressGestureRecognizer instance) {
|
||||
instance.onLongPressStart = onLongPressStart;
|
||||
});
|
||||
|
||||
@@ -198,16 +191,14 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
|
||||
for (final int pointer in _pointerLocations.keys) {
|
||||
focalPoint += _pointerLocations[pointer]!;
|
||||
}
|
||||
_currentFocalPoint =
|
||||
count > 0 ? focalPoint / count.toDouble() : Offset.zero;
|
||||
_currentFocalPoint = count > 0 ? focalPoint / count.toDouble() : Offset.zero;
|
||||
|
||||
// Span is the average deviation from focal point. Horizontal and vertical
|
||||
// spans are the average deviations from the focal point's horizontal and
|
||||
// vertical coordinates, respectively.
|
||||
double totalDeviation = 0.0;
|
||||
for (final int pointer in _pointerLocations.keys) {
|
||||
totalDeviation +=
|
||||
(_currentFocalPoint! - _pointerLocations[pointer]!).distance;
|
||||
totalDeviation += (_currentFocalPoint! - _pointerLocations[pointer]!).distance;
|
||||
}
|
||||
_currentSpan = count > 0 ? totalDeviation / count : 0.0;
|
||||
}
|
||||
@@ -219,15 +210,13 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
|
||||
: hitDetector!.shouldMove(move, Axis.horizontal);
|
||||
if (shouldMove || _pointerLocations.keys.length > 1) {
|
||||
final double spanDelta = (_currentSpan! - _initialSpan!).abs();
|
||||
final double focalPointDelta =
|
||||
(_currentFocalPoint! - _initialFocalPoint!).distance;
|
||||
final double focalPointDelta = (_currentFocalPoint! - _initialFocalPoint!).distance;
|
||||
// warning: do not compare `focalPointDelta` to `kPanSlop`
|
||||
// `ScaleGestureRecognizer` uses `kPanSlop`, but `HorizontalDragGestureRecognizer` uses `kTouchSlop`
|
||||
// and PhotoView recognizer may compete with the `HorizontalDragGestureRecognizer` from a containing `PageView`
|
||||
// setting `touchSlopFactor` to 2 restores default `ScaleGestureRecognizer` behaviour as `kPanSlop = kTouchSlop * 2.0`
|
||||
// setting `touchSlopFactor` in [0, 1] will allow this recognizer to accept the gesture before the one from `PageView`
|
||||
if (spanDelta > kScaleSlop ||
|
||||
focalPointDelta > kTouchSlop * touchSlopFactor) {
|
||||
if (spanDelta > kScaleSlop || focalPointDelta > kTouchSlop * touchSlopFactor) {
|
||||
acceptGesture(event.pointer);
|
||||
}
|
||||
}
|
||||
@@ -260,8 +249,8 @@ class PhotoViewGestureDetectorScope extends InheritedWidget {
|
||||
});
|
||||
|
||||
static PhotoViewGestureDetectorScope? of(BuildContext context) {
|
||||
final PhotoViewGestureDetectorScope? scope = context
|
||||
.dependOnInheritedWidgetOfExactType<PhotoViewGestureDetectorScope>();
|
||||
final PhotoViewGestureDetectorScope? scope =
|
||||
context.dependOnInheritedWidgetOfExactType<PhotoViewGestureDetectorScope>();
|
||||
return scope;
|
||||
}
|
||||
|
||||
@@ -275,8 +264,7 @@ class PhotoViewGestureDetectorScope extends InheritedWidget {
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(PhotoViewGestureDetectorScope oldWidget) {
|
||||
return axis != oldWidget.axis &&
|
||||
touchSlopFactor != oldWidget.touchSlopFactor;
|
||||
return axis != oldWidget.axis && touchSlopFactor != oldWidget.touchSlopFactor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ mixin HitCornersDetector on PhotoViewControllerDelegate {
|
||||
if (!hitCorners.hasHitAny) {
|
||||
return true;
|
||||
}
|
||||
final axisBlocked = hitCorners.hasHitBoth ||
|
||||
(hitCorners.hasHitMax ? mainAxisMove > 0 : mainAxisMove < 0);
|
||||
final axisBlocked = hitCorners.hasHitBoth || (hitCorners.hasHitMax ? mainAxisMove > 0 : mainAxisMove < 0);
|
||||
if (axisBlocked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,7 @@ class PhotoViewComputedScale {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is PhotoViewComputedScale &&
|
||||
runtimeType == other.runtimeType &&
|
||||
_value == other._value;
|
||||
other is PhotoViewComputedScale && runtimeType == other.runtimeType && _value == other._value;
|
||||
|
||||
@override
|
||||
int get hashCode => _value.hashCode;
|
||||
|
||||
@@ -29,9 +29,7 @@ class PhotoViewDefaultLoading extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final expectedBytes = event?.expectedTotalBytes;
|
||||
final loadedBytes = event?.cumulativeBytesLoaded;
|
||||
final value = loadedBytes != null && expectedBytes != null
|
||||
? loadedBytes / expectedBytes
|
||||
: null;
|
||||
final value = loadedBytes != null && expectedBytes != null ? loadedBytes / expectedBytes : null;
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
|
||||
@@ -6,7 +6,5 @@ enum PhotoViewScaleState {
|
||||
zoomedIn,
|
||||
zoomedOut;
|
||||
|
||||
bool get isScaleStateZooming =>
|
||||
this == PhotoViewScaleState.zoomedIn ||
|
||||
this == PhotoViewScaleState.zoomedOut;
|
||||
bool get isScaleStateZooming => this == PhotoViewScaleState.zoomedIn || this == PhotoViewScaleState.zoomedOut;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ import 'package:flutter/foundation.dart';
|
||||
/// The common collection of listeners inherited from [ChangeNotifier] will be fired
|
||||
/// every time.
|
||||
class IgnorableChangeNotifier extends ChangeNotifier {
|
||||
ObserverList<VoidCallback>? _ignorableListeners =
|
||||
ObserverList<VoidCallback>();
|
||||
ObserverList<VoidCallback>? _ignorableListeners = ObserverList<VoidCallback>();
|
||||
|
||||
bool _debugAssertNotDisposed() {
|
||||
assert(() {
|
||||
@@ -51,8 +50,7 @@ class IgnorableChangeNotifier extends ChangeNotifier {
|
||||
void notifyListeners() {
|
||||
super.notifyListeners();
|
||||
if (_ignorableListeners != null) {
|
||||
final List<VoidCallback> localListeners =
|
||||
List<VoidCallback>.from(_ignorableListeners!);
|
||||
final List<VoidCallback> localListeners = List<VoidCallback>.from(_ignorableListeners!);
|
||||
for (VoidCallback listener in localListeners) {
|
||||
try {
|
||||
if (_ignorableListeners!.contains(listener)) {
|
||||
@@ -80,8 +78,7 @@ class IgnorableChangeNotifier extends ChangeNotifier {
|
||||
|
||||
/// Just like [ValueNotifier] except it extends [IgnorableChangeNotifier] which has
|
||||
/// listeners that wont fire when [updateIgnoring] is called.
|
||||
class IgnorableValueNotifier<T> extends IgnorableChangeNotifier
|
||||
implements ValueListenable<T> {
|
||||
class IgnorableValueNotifier<T> extends IgnorableChangeNotifier implements ValueListenable<T> {
|
||||
IgnorableValueNotifier(this._value);
|
||||
|
||||
@override
|
||||
|
||||
@@ -101,11 +101,7 @@ class ScaleBoundaries {
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
_minScale.hashCode ^
|
||||
_maxScale.hashCode ^
|
||||
_initialScale.hashCode ^
|
||||
outerSize.hashCode ^
|
||||
childSize.hashCode;
|
||||
_minScale.hashCode ^ _maxScale.hashCode ^ _initialScale.hashCode ^ outerSize.hashCode ^ childSize.hashCode;
|
||||
}
|
||||
|
||||
double _scaleForContained(Size size, Size childSize) {
|
||||
|
||||
@@ -45,8 +45,7 @@ class CuratedPlacesRow extends StatelessWidget {
|
||||
}
|
||||
final actualIndex = index - actualContentIndex;
|
||||
final object = content[actualIndex];
|
||||
final thumbnailRequestUrl =
|
||||
'${Store.get(StoreKey.serverEndpoint)}/assets/${object.id}/thumbnail';
|
||||
final thumbnailRequestUrl = '${Store.get(StoreKey.serverEndpoint)}/assets/${object.id}/thumbnail';
|
||||
return SizedBox.square(
|
||||
dimension: imageSize,
|
||||
child: ThumbnailWithInfo(
|
||||
|
||||
@@ -15,8 +15,7 @@ class LocationPicker extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final countryTextController =
|
||||
useTextEditingController(text: filter?.country);
|
||||
final countryTextController = useTextEditingController(text: filter?.country);
|
||||
final stateTextController = useTextEditingController(text: filter?.state);
|
||||
final cityTextController = useTextEditingController(text: filter?.city);
|
||||
|
||||
|
||||
@@ -52,18 +52,14 @@ class PeoplePicker extends HookConsumerWidget {
|
||||
shrinkWrap: true,
|
||||
itemCount: people
|
||||
.where(
|
||||
(person) => person.name
|
||||
.toLowerCase()
|
||||
.contains(searchQuery.value.toLowerCase()),
|
||||
(person) => person.name.toLowerCase().contains(searchQuery.value.toLowerCase()),
|
||||
)
|
||||
.length,
|
||||
padding: const EdgeInsets.all(8),
|
||||
itemBuilder: (context, index) {
|
||||
final person = people
|
||||
.where(
|
||||
(person) => person.name
|
||||
.toLowerCase()
|
||||
.contains(searchQuery.value.toLowerCase()),
|
||||
(person) => person.name.toLowerCase().contains(searchQuery.value.toLowerCase()),
|
||||
)
|
||||
.toList()[index];
|
||||
final isSelected = selectedPeople.value.contains(person);
|
||||
@@ -76,9 +72,7 @@ class PeoplePicker extends HookConsumerWidget {
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isSelected
|
||||
? context.colorScheme.onPrimary
|
||||
: context.colorScheme.onSurface,
|
||||
color: isSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
leading: SizedBox(
|
||||
|
||||
@@ -27,8 +27,7 @@ class SearchFilterChip extends StatelessWidget {
|
||||
side: BorderSide(color: context.colorScheme.secondaryContainer),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
|
||||
@@ -22,8 +22,7 @@ class ThumbnailWithInfo extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var textAndIconColor =
|
||||
context.isDarkTheme ? Colors.grey[100] : Colors.grey[700];
|
||||
var textAndIconColor = context.isDarkTheme ? Colors.grey[100] : Colors.grey[700];
|
||||
return ThumbnailWithInfoContainer(
|
||||
onTap: onTap,
|
||||
borderRadius: borderRadius,
|
||||
@@ -37,8 +36,7 @@ class ThumbnailWithInfo extends StatelessWidget {
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: imageUrl!,
|
||||
httpHeaders: ApiService.getRequestHeaders(),
|
||||
errorWidget: (context, url, error) =>
|
||||
const Icon(Icons.image_not_supported_outlined),
|
||||
errorWidget: (context, url, error) => const Icon(Icons.image_not_supported_outlined),
|
||||
),
|
||||
)
|
||||
: Center(
|
||||
|
||||
@@ -43,9 +43,7 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
|
||||
end: FractionalOffset.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
label == ''
|
||||
? Colors.black.withValues(alpha: 0.1)
|
||||
: Colors.black.withValues(alpha: 0.5),
|
||||
label == '' ? Colors.black.withValues(alpha: 0.1) : Colors.black.withValues(alpha: 0.5),
|
||||
],
|
||||
stops: const [0.0, 1.0],
|
||||
),
|
||||
@@ -53,8 +51,7 @@ class ThumbnailWithInfoContainer extends StatelessWidget {
|
||||
child: child,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8) +
|
||||
const EdgeInsets.only(bottom: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8) + const EdgeInsets.only(bottom: 8),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
|
||||
@@ -25,23 +25,18 @@ class AdvancedSettings extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
bool isLoggedIn = ref.read(currentUserProvider) != null;
|
||||
|
||||
final advancedTroubleshooting =
|
||||
useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
||||
final manageLocalMediaAndroid =
|
||||
useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
||||
final advancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
||||
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
||||
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
|
||||
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
|
||||
final allowSelfSignedSSLCert =
|
||||
useAppSettingsState(AppSettingsEnum.allowSelfSignedSSLCert);
|
||||
final useAlternatePMFilter =
|
||||
useAppSettingsState(AppSettingsEnum.photoManagerCustomFilter);
|
||||
final allowSelfSignedSSLCert = useAppSettingsState(AppSettingsEnum.allowSelfSignedSSLCert);
|
||||
final useAlternatePMFilter = useAppSettingsState(AppSettingsEnum.photoManagerCustomFilter);
|
||||
|
||||
final logLevel = Level.LEVELS[levelId.value].name;
|
||||
|
||||
useValueChanged(
|
||||
levelId.value,
|
||||
(_, __) =>
|
||||
LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel()),
|
||||
(_, __) => LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel()),
|
||||
);
|
||||
|
||||
Future<bool> checkAndroidVersion() async {
|
||||
@@ -72,9 +67,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
||||
subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(),
|
||||
onChanged: (value) async {
|
||||
if (value) {
|
||||
final result = await ref
|
||||
.read(localFilesManagerRepositoryProvider)
|
||||
.requestManageMediaPermission();
|
||||
final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission();
|
||||
manageLocalMediaAndroid.value = result;
|
||||
}
|
||||
},
|
||||
@@ -85,8 +78,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
||||
},
|
||||
),
|
||||
SettingsSliderListTile(
|
||||
text: "advanced_settings_log_level_title"
|
||||
.tr(namedArgs: {'level': logLevel}),
|
||||
text: "advanced_settings_log_level_title".tr(namedArgs: {'level': logLevel}),
|
||||
valueNotifier: levelId,
|
||||
maxValue: 8,
|
||||
minValue: 1,
|
||||
@@ -111,8 +103,7 @@ class AdvancedSettings extends HookConsumerWidget {
|
||||
SettingsSwitchListTile(
|
||||
valueNotifier: useAlternatePMFilter,
|
||||
title: "advanced_settings_enable_alternate_media_filter_title".tr(),
|
||||
subtitle:
|
||||
"advanced_settings_enable_alternate_media_filter_subtitle".tr(),
|
||||
subtitle: "advanced_settings_enable_alternate_media_filter_subtitle".tr(),
|
||||
),
|
||||
];
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ class LayoutSettings extends HookConsumerWidget {
|
||||
),
|
||||
SettingsSliderListTile(
|
||||
valueNotifier: tilesPerRow,
|
||||
text: 'theme_setting_asset_list_tiles_per_row_title'
|
||||
.tr(namedArgs: {'count': "${tilesPerRow.value}"}),
|
||||
text: 'theme_setting_asset_list_tiles_per_row_title'.tr(namedArgs: {'count': "${tilesPerRow.value}"}),
|
||||
label: "${tilesPerRow.value}",
|
||||
maxValue: 6,
|
||||
minValue: 2,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user