chore: bump line length to 120 (#20191)

This commit is contained in:
shenlong
2025-07-25 08:07:22 +05:30
committed by GitHub
parent 977c9b96ba
commit ad65e9011a
517 changed files with 4520 additions and 9514 deletions
@@ -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;
},
),
),
+7 -16
View File
@@ -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;
}
+1 -2
View File
@@ -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;
}
+1 -2
View File
@@ -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,
+1 -2
View File
@@ -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),
);
}
+1 -2
View File
@@ -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();
+14 -32
View File
@@ -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();
}
+2 -4
View File
@@ -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),
+2 -3
View File
@@ -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(),
),
);
}
+10 -23
View File
@@ -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,
),
),
+2 -5
View File
@@ -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)),
+8 -15
View File
@@ -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),
],
+5 -11
View File
@@ -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);
+11 -16
View File
@@ -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),
+2 -4
View File
@@ -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,
+5 -11
View File
@@ -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