mirror of
https://github.com/immich-app/immich.git
synced 2025-12-24 01:11:32 +03:00
chore: bump dart sdk to 3.8 (#20355)
* chore: bump dart sdk to 3.8 * chore: make build * make pigeon * chore: format files --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@@ -163,12 +163,7 @@ class AssetIndexWrapper extends SingleChildRenderObjectWidget {
|
||||
final int rowIndex;
|
||||
final int sectionIndex;
|
||||
|
||||
const AssetIndexWrapper({
|
||||
required Widget super.child,
|
||||
required this.rowIndex,
|
||||
required this.sectionIndex,
|
||||
super.key,
|
||||
});
|
||||
const AssetIndexWrapper({required Widget super.child, required this.rowIndex, required this.sectionIndex, super.key});
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
@@ -191,19 +186,14 @@ class AssetIndexWrapper extends SingleChildRenderObjectWidget {
|
||||
class _AssetIndexProxy extends RenderProxyBox {
|
||||
AssetIndex index;
|
||||
|
||||
_AssetIndexProxy({
|
||||
required this.index,
|
||||
});
|
||||
_AssetIndexProxy({required this.index});
|
||||
}
|
||||
|
||||
class AssetIndex {
|
||||
final int rowIndex;
|
||||
final int sectionIndex;
|
||||
|
||||
const AssetIndex({
|
||||
required this.rowIndex,
|
||||
required this.sectionIndex,
|
||||
});
|
||||
const AssetIndex({required this.rowIndex, required this.sectionIndex});
|
||||
|
||||
@override
|
||||
bool operator ==(covariant AssetIndex other) {
|
||||
|
||||
@@ -8,12 +8,7 @@ import 'package:logging/logging.dart';
|
||||
|
||||
final log = Logger('AssetGridDataStructure');
|
||||
|
||||
enum RenderAssetGridElementType {
|
||||
assets,
|
||||
assetRow,
|
||||
groupDividerTitle,
|
||||
monthTitle;
|
||||
}
|
||||
enum RenderAssetGridElementType { assets, assetRow, groupDividerTitle, monthTitle }
|
||||
|
||||
class RenderAssetGridElement {
|
||||
final RenderAssetGridElementType type;
|
||||
@@ -33,13 +28,7 @@ class RenderAssetGridElement {
|
||||
});
|
||||
}
|
||||
|
||||
enum GroupAssetsBy {
|
||||
day,
|
||||
month,
|
||||
auto,
|
||||
none,
|
||||
;
|
||||
}
|
||||
enum GroupAssetsBy { day, month, auto, none }
|
||||
|
||||
class RenderList {
|
||||
final List<RenderAssetGridElement> elements;
|
||||
@@ -87,10 +76,7 @@ class RenderList {
|
||||
// when scrolling backward, end shortly after the requested offset...
|
||||
// ... to guard against the user scrolling in the other direction
|
||||
// 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),
|
||||
);
|
||||
final start = max(0, 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();
|
||||
_bufOffset = start;
|
||||
@@ -117,19 +103,14 @@ class RenderList {
|
||||
// request the asset from the database (not changing the buffer!)
|
||||
final asset = query!.offset(index).findFirstSync();
|
||||
if (asset == null) {
|
||||
throw Exception(
|
||||
"Asset at index $index does no longer exist in database",
|
||||
);
|
||||
throw Exception("Asset at index $index does no longer exist in database");
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
throw Exception("RenderList has neither assets nor query");
|
||||
}
|
||||
|
||||
static Future<RenderList> fromQuery(
|
||||
QueryBuilder<Asset, Asset, QAfterSortBy> query,
|
||||
GroupAssetsBy groupBy,
|
||||
) =>
|
||||
static Future<RenderList> fromQuery(QueryBuilder<Asset, Asset, QAfterSortBy> query, GroupAssetsBy groupBy) =>
|
||||
_buildRenderList(null, query, groupBy);
|
||||
|
||||
static Future<RenderList> _buildRenderList(
|
||||
@@ -145,12 +126,7 @@ class RenderList {
|
||||
if (groupBy == GroupAssetsBy.none) {
|
||||
final int total = assets?.length ?? query!.countSync();
|
||||
|
||||
final dateLoader = query != null
|
||||
? DateBatchLoader(
|
||||
query: query,
|
||||
batchSize: 1000 * sectionSize,
|
||||
)
|
||||
: null;
|
||||
final dateLoader = query != null ? DateBatchLoader(query: query, batchSize: 1000 * sectionSize) : null;
|
||||
|
||||
for (int i = 0; i < total; i += sectionSize) {
|
||||
final date = assets != null ? assets[i].fileCreatedAt : await dateLoader?.getDate(i);
|
||||
@@ -224,11 +200,11 @@ class RenderList {
|
||||
for (int j = 0; j < count; j += sectionSize) {
|
||||
final type = j == 0
|
||||
? (groupBy != GroupAssetsBy.month && newMonth
|
||||
? RenderAssetGridElementType.monthTitle
|
||||
: RenderAssetGridElementType.groupDividerTitle)
|
||||
? RenderAssetGridElementType.monthTitle
|
||||
: RenderAssetGridElementType.groupDividerTitle)
|
||||
: (groupBy == GroupAssetsBy.auto
|
||||
? RenderAssetGridElementType.groupDividerTitle
|
||||
: RenderAssetGridElementType.assets);
|
||||
? RenderAssetGridElementType.groupDividerTitle
|
||||
: RenderAssetGridElementType.assets);
|
||||
final sectionCount = j + sectionSize > count ? count - j : sectionSize;
|
||||
assert(sectionCount > 0 && sectionCount <= sectionSize);
|
||||
elements.add(
|
||||
@@ -257,11 +233,7 @@ class RenderList {
|
||||
: await query!.offset(offset).limit(pageSize).fileCreatedAtProperty().findAll();
|
||||
int i = 0;
|
||||
for (final date in dates) {
|
||||
final d = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
groupBy == GroupAssetsBy.month ? 1 : date.day,
|
||||
);
|
||||
final d = DateTime(date.year, date.month, groupBy == GroupAssetsBy.month ? 1 : date.day);
|
||||
current ??= d;
|
||||
if (current != d) {
|
||||
addElems(current, prevDate);
|
||||
@@ -288,10 +260,7 @@ class RenderList {
|
||||
|
||||
static RenderList empty() => RenderList([], null, []);
|
||||
|
||||
static Future<RenderList> fromAssets(
|
||||
List<Asset> assets,
|
||||
GroupAssetsBy groupBy,
|
||||
) =>
|
||||
static Future<RenderList> fromAssets(List<Asset> assets, GroupAssetsBy groupBy) =>
|
||||
_buildRenderList(assets, null, groupBy);
|
||||
|
||||
/// Deletes an asset from the render list and clears the buffer
|
||||
@@ -310,10 +279,7 @@ class DateBatchLoader {
|
||||
List<DateTime> _buffer = [];
|
||||
int _bufferStart = 0;
|
||||
|
||||
DateBatchLoader({
|
||||
required this.query,
|
||||
required this.batchSize,
|
||||
});
|
||||
DateBatchLoader({required this.query, required this.batchSize});
|
||||
|
||||
Future<DateTime?> getDate(int index) async {
|
||||
if (!_isIndexInBuffer(index)) {
|
||||
|
||||
@@ -81,43 +81,26 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||
|
||||
void minimize() {
|
||||
scrollController.animateTo(
|
||||
bottomPadding,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
scrollController.animateTo(bottomPadding, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
controlBottomAppBarNotifier.addListener(minimize);
|
||||
return () {
|
||||
controlBottomAppBarNotifier.removeListener(minimize);
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
controlBottomAppBarNotifier.addListener(minimize);
|
||||
return () {
|
||||
controlBottomAppBarNotifier.removeListener(minimize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
void showForceDeleteDialog(
|
||||
Function(bool) deleteCb, {
|
||||
String? alertMsg,
|
||||
}) {
|
||||
void showForceDeleteDialog(Function(bool) deleteCb, {String? alertMsg}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DeleteDialog(
|
||||
alert: alertMsg,
|
||||
onDelete: () => deleteCb(true),
|
||||
);
|
||||
return DeleteDialog(alert: alertMsg, onDelete: () => deleteCb(true));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void handleRemoteDelete(
|
||||
bool force,
|
||||
Function(bool) deleteCb, {
|
||||
String? alertMsg,
|
||||
}) {
|
||||
void handleRemoteDelete(bool force, Function(bool) deleteCb, {String? alertMsg}) {
|
||||
if (!force) {
|
||||
deleteCb(force);
|
||||
return;
|
||||
@@ -153,11 +136,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
if (hasRemote && onDownload != null)
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 90),
|
||||
child: ControlBoxButton(
|
||||
iconData: Icons.download,
|
||||
label: "download".tr(),
|
||||
onPressed: onDownload,
|
||||
),
|
||||
child: ControlBoxButton(iconData: Icons.download, label: "download".tr(), onPressed: onDownload),
|
||||
),
|
||||
if (hasLocal && hasRemote && onDelete != null && !isInLockedView)
|
||||
ConstrainedBox(
|
||||
@@ -178,17 +157,10 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
? "control_bottom_app_bar_trash_from_immich".tr()
|
||||
: "control_bottom_app_bar_delete_from_immich".tr(),
|
||||
onPressed: enabled
|
||||
? () => handleRemoteDelete(
|
||||
!trashEnabled,
|
||||
onDeleteServer!,
|
||||
alertMsg: "delete_dialog_alert_remote",
|
||||
)
|
||||
? () => handleRemoteDelete(!trashEnabled, onDeleteServer!, alertMsg: "delete_dialog_alert_remote")
|
||||
: null,
|
||||
onLongPressed: enabled
|
||||
? () => showForceDeleteDialog(
|
||||
onDeleteServer!,
|
||||
alertMsg: "delete_dialog_alert_remote",
|
||||
)
|
||||
? () => showForceDeleteDialog(onDeleteServer!, alertMsg: "delete_dialog_alert_remote")
|
||||
: null,
|
||||
),
|
||||
),
|
||||
@@ -199,10 +171,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
iconData: Icons.delete_forever,
|
||||
label: "delete_dialog_title".tr(),
|
||||
onPressed: enabled
|
||||
? () => showForceDeleteDialog(
|
||||
onDeleteServer!,
|
||||
alertMsg: "delete_dialog_alert_remote",
|
||||
)
|
||||
? () => showForceDeleteDialog(onDeleteServer!, alertMsg: "delete_dialog_alert_remote")
|
||||
: null,
|
||||
),
|
||||
),
|
||||
@@ -221,9 +190,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DeleteLocalOnlyDialog(
|
||||
onDeleteLocal: onDeleteLocal!,
|
||||
);
|
||||
return DeleteLocalOnlyDialog(onDeleteLocal: onDeleteLocal!);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -281,13 +248,11 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
label: "upload".tr(),
|
||||
onPressed: enabled
|
||||
? () => showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return UploadDialog(
|
||||
onUpload: onUpload,
|
||||
);
|
||||
},
|
||||
)
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return UploadDialog(onUpload: onUpload);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
];
|
||||
@@ -325,10 +290,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
surfaceTintColor: context.colorScheme.surfaceContainerHigh,
|
||||
elevation: 6.0,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(0),
|
||||
child: CustomScrollView(
|
||||
@@ -349,14 +311,8 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
if (hasRemote && !isInLockedView) ...[
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
thickness: 1,
|
||||
),
|
||||
_AddToAlbumTitleRow(
|
||||
onCreateNewAlbum: enabled ? onCreateNewAlbum : null,
|
||||
),
|
||||
const Divider(indent: 16, endIndent: 16, thickness: 1),
|
||||
_AddToAlbumTitleRow(onCreateNewAlbum: enabled ? onCreateNewAlbum : null),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -380,9 +336,7 @@ class ControlBottomAppBar extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
class _AddToAlbumTitleRow extends StatelessWidget {
|
||||
const _AddToAlbumTitleRow({
|
||||
required this.onCreateNewAlbum,
|
||||
});
|
||||
const _AddToAlbumTitleRow({required this.onCreateNewAlbum});
|
||||
|
||||
final VoidCallback? onCreateNewAlbum;
|
||||
|
||||
@@ -393,23 +347,13 @@ class _AddToAlbumTitleRow extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"add_to_album",
|
||||
style: context.textTheme.titleSmall,
|
||||
).tr(),
|
||||
Text("add_to_album", style: context.textTheme.titleSmall).tr(),
|
||||
TextButton.icon(
|
||||
onPressed: onCreateNewAlbum,
|
||||
icon: Icon(
|
||||
Icons.add,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.add, color: context.primaryColor),
|
||||
label: Text(
|
||||
"common_create_new_album",
|
||||
style: TextStyle(
|
||||
color: context.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 14),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -5,22 +5,19 @@ import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
||||
|
||||
class DeleteDialog extends ConfirmDialog {
|
||||
const DeleteDialog({super.key, String? alert, required Function onDelete})
|
||||
: super(
|
||||
title: "delete_dialog_title",
|
||||
content: alert ?? "delete_dialog_alert",
|
||||
cancel: "cancel",
|
||||
ok: "delete",
|
||||
onOk: onDelete,
|
||||
);
|
||||
: super(
|
||||
title: "delete_dialog_title",
|
||||
content: alert ?? "delete_dialog_alert",
|
||||
cancel: "cancel",
|
||||
ok: "delete",
|
||||
onOk: onDelete,
|
||||
);
|
||||
}
|
||||
|
||||
class DeleteLocalOnlyDialog extends StatelessWidget {
|
||||
final void Function(bool onlyMerged) onDeleteLocal;
|
||||
|
||||
const DeleteLocalOnlyDialog({
|
||||
super.key,
|
||||
required this.onDeleteLocal,
|
||||
});
|
||||
const DeleteLocalOnlyDialog({super.key, required this.onDeleteLocal});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -35,9 +32,7 @@ class DeleteLocalOnlyDialog extends StatelessWidget {
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
title: const Text("delete_dialog_title").tr(),
|
||||
content: const Text("delete_dialog_alert_local_non_backed_up").tr(),
|
||||
actions: [
|
||||
@@ -45,30 +40,21 @@ class DeleteLocalOnlyDialog extends StatelessWidget {
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(
|
||||
"cancel",
|
||||
style: TextStyle(
|
||||
color: context.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onDeleteBackedUpOnly,
|
||||
child: Text(
|
||||
"delete_local_dialog_ok_backed_up_only",
|
||||
style: TextStyle(
|
||||
color: context.colorScheme.tertiary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(color: context.colorScheme.tertiary, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onForceDelete,
|
||||
child: Text(
|
||||
"delete_local_dialog_ok_force",
|
||||
style: TextStyle(
|
||||
color: Colors.red[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(color: Colors.red[400], fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -3,11 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
||||
class DisableMultiSelectButton extends ConsumerWidget {
|
||||
const DisableMultiSelectButton({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.selectedItemCount,
|
||||
});
|
||||
const DisableMultiSelectButton({super.key, required this.onPressed, required this.selectedItemCount});
|
||||
|
||||
final Function onPressed;
|
||||
final int selectedItemCount;
|
||||
@@ -22,16 +18,10 @@ class DisableMultiSelectButton extends ConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => onPressed(),
|
||||
icon: Icon(
|
||||
Icons.close_rounded,
|
||||
color: context.colorScheme.onPrimary,
|
||||
),
|
||||
icon: Icon(Icons.close_rounded, color: context.colorScheme.onPrimary),
|
||||
label: Text(
|
||||
'$selectedItemCount',
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
height: 2.5,
|
||||
color: context.colorScheme.onPrimary,
|
||||
),
|
||||
style: context.textTheme.titleMedium?.copyWith(height: 2.5, color: context.colorScheme.onPrimary),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -3,14 +3,15 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Build the Scroll Thumb and label using the current configuration
|
||||
typedef ScrollThumbBuilder = Widget Function(
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
Animation<double> labelAnimation,
|
||||
double height, {
|
||||
Text? labelText,
|
||||
BoxConstraints? labelConstraints,
|
||||
});
|
||||
typedef ScrollThumbBuilder =
|
||||
Widget Function(
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
Animation<double> labelAnimation,
|
||||
double height, {
|
||||
Text? labelText,
|
||||
BoxConstraints? labelConstraints,
|
||||
});
|
||||
|
||||
/// Build a Text widget using the current scroll offset
|
||||
typedef LabelTextBuilder = Text Function(double offsetY);
|
||||
@@ -79,8 +80,8 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
|
||||
this.labelTextBuilder,
|
||||
this.labelConstraints,
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbRRectBuilder(alwaysVisibleScrollThumb);
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbRRectBuilder(alwaysVisibleScrollThumb);
|
||||
|
||||
DraggableScrollbar.arrows({
|
||||
super.key,
|
||||
@@ -95,8 +96,8 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
|
||||
this.labelTextBuilder,
|
||||
this.labelConstraints,
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbArrowBuilder(alwaysVisibleScrollThumb);
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbArrowBuilder(alwaysVisibleScrollThumb);
|
||||
|
||||
DraggableScrollbar.semicircle({
|
||||
super.key,
|
||||
@@ -111,12 +112,8 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
|
||||
this.labelTextBuilder,
|
||||
this.labelConstraints,
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbSemicircleBuilder(
|
||||
heightScrollThumb * 0.6,
|
||||
scrollThumbKey,
|
||||
alwaysVisibleScrollThumb,
|
||||
);
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbSemicircleBuilder(heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb);
|
||||
|
||||
@override
|
||||
DraggableScrollbarState createState() => DraggableScrollbarState();
|
||||
@@ -149,17 +146,10 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
if (alwaysVisibleScrollThumb) {
|
||||
return scrollThumbAndLabel;
|
||||
}
|
||||
return SlideFadeTransition(
|
||||
animation: thumbAnimation!,
|
||||
child: scrollThumbAndLabel,
|
||||
);
|
||||
return SlideFadeTransition(animation: thumbAnimation!, child: scrollThumbAndLabel);
|
||||
}
|
||||
|
||||
static ScrollThumbBuilder _thumbSemicircleBuilder(
|
||||
double width,
|
||||
Key? scrollThumbKey,
|
||||
bool alwaysVisibleScrollThumb,
|
||||
) {
|
||||
static ScrollThumbBuilder _thumbSemicircleBuilder(double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
|
||||
return (
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
@@ -180,9 +170,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
topRight: const Radius.circular(4.0),
|
||||
bottomRight: const Radius.circular(4.0),
|
||||
),
|
||||
child: Container(
|
||||
constraints: BoxConstraints.tight(Size(width, height)),
|
||||
),
|
||||
child: Container(constraints: BoxConstraints.tight(Size(width, height))),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -198,9 +186,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
};
|
||||
}
|
||||
|
||||
static ScrollThumbBuilder _thumbArrowBuilder(
|
||||
bool alwaysVisibleScrollThumb,
|
||||
) {
|
||||
static ScrollThumbBuilder _thumbArrowBuilder(bool alwaysVisibleScrollThumb) {
|
||||
return (
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
@@ -216,9 +202,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
width: 20.0,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -235,9 +219,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
};
|
||||
}
|
||||
|
||||
static ScrollThumbBuilder _thumbRRectBuilder(
|
||||
bool alwaysVisibleScrollThumb,
|
||||
) {
|
||||
static ScrollThumbBuilder _thumbRRectBuilder(bool alwaysVisibleScrollThumb) {
|
||||
return (
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
@@ -250,11 +232,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
elevation: 4.0,
|
||||
color: backgroundColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(7.0)),
|
||||
child: Container(
|
||||
constraints: BoxConstraints.tight(
|
||||
Size(16.0, height),
|
||||
),
|
||||
),
|
||||
child: Container(constraints: BoxConstraints.tight(Size(16.0, height))),
|
||||
);
|
||||
|
||||
return buildScrollThumbAndLabel(
|
||||
@@ -296,11 +274,7 @@ class ScrollLabel extends StatelessWidget {
|
||||
elevation: 4.0,
|
||||
color: backgroundColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
||||
child: Container(
|
||||
constraints: constraints ?? _defaultConstraints,
|
||||
alignment: Alignment.center,
|
||||
child: child,
|
||||
),
|
||||
child: Container(constraints: constraints ?? _defaultConstraints, alignment: Alignment.center, child: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -325,25 +299,13 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
_viewOffset = 0.0;
|
||||
_isDragInProcess = false;
|
||||
|
||||
_thumbAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.scrollbarAnimationDuration,
|
||||
);
|
||||
_thumbAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration);
|
||||
|
||||
_thumbAnimation = CurvedAnimation(
|
||||
parent: _thumbAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
_thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastOutSlowIn);
|
||||
|
||||
_labelAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.scrollbarAnimationDuration,
|
||||
);
|
||||
_labelAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration);
|
||||
|
||||
_labelAnimation = CurvedAnimation(
|
||||
parent: _labelAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
_labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -366,9 +328,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
Widget build(BuildContext context) {
|
||||
Text? labelText;
|
||||
if (widget.labelTextBuilder != null && _isDragInProcess) {
|
||||
labelText = widget.labelTextBuilder!(
|
||||
_viewOffset + _barOffset + widget.heightScrollThumb / 2,
|
||||
);
|
||||
labelText = widget.labelTextBuilder!(_viewOffset + _barOffset + widget.heightScrollThumb / 2);
|
||||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
@@ -382,9 +342,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
},
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
RepaintBoundary(
|
||||
child: widget.child,
|
||||
),
|
||||
RepaintBoundary(child: widget.child),
|
||||
RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
onVerticalDragStart: _onVerticalDragStart,
|
||||
@@ -422,11 +380,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
|
||||
setState(() {
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
_barOffset += getBarDelta(
|
||||
notification.scrollDelta!,
|
||||
barMaxScrollExtent,
|
||||
viewMaxScrollExtent,
|
||||
);
|
||||
_barOffset += getBarDelta(notification.scrollDelta!, barMaxScrollExtent, viewMaxScrollExtent);
|
||||
|
||||
if (_barOffset < barMinScrollExtent) {
|
||||
_barOffset = barMinScrollExtent;
|
||||
@@ -459,19 +413,11 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
});
|
||||
}
|
||||
|
||||
double getBarDelta(
|
||||
double scrollViewDelta,
|
||||
double barMaxScrollExtent,
|
||||
double viewMaxScrollExtent,
|
||||
) {
|
||||
double getBarDelta(double scrollViewDelta, double barMaxScrollExtent, double viewMaxScrollExtent) {
|
||||
return scrollViewDelta * barMaxScrollExtent / viewMaxScrollExtent;
|
||||
}
|
||||
|
||||
double getScrollViewDelta(
|
||||
double barDelta,
|
||||
double barMaxScrollExtent,
|
||||
double viewMaxScrollExtent,
|
||||
) {
|
||||
double getScrollViewDelta(double barDelta, double barMaxScrollExtent, double viewMaxScrollExtent) {
|
||||
return barDelta * viewMaxScrollExtent / barMaxScrollExtent;
|
||||
}
|
||||
|
||||
@@ -498,11 +444,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
_barOffset = barMaxScrollExtent;
|
||||
}
|
||||
|
||||
double viewDelta = getScrollViewDelta(
|
||||
details.delta.dy,
|
||||
barMaxScrollExtent,
|
||||
viewMaxScrollExtent,
|
||||
);
|
||||
double viewDelta = getScrollViewDelta(details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent);
|
||||
|
||||
_viewOffset = widget.controller.position.pixels + viewDelta;
|
||||
if (_viewOffset < widget.controller.position.minScrollExtent) {
|
||||
@@ -545,14 +487,8 @@ class ArrowCustomPainter extends CustomPainter {
|
||||
final baseX = size.width / 2;
|
||||
final baseY = size.height / 2;
|
||||
|
||||
canvas.drawPath(
|
||||
_trianglePath(Offset(baseX, baseY - 2.0), width, height, true),
|
||||
paint,
|
||||
);
|
||||
canvas.drawPath(
|
||||
_trianglePath(Offset(baseX, baseY + 2.0), width, height, false),
|
||||
paint,
|
||||
);
|
||||
canvas.drawPath(_trianglePath(Offset(baseX, baseY - 2.0), width, height, true), paint);
|
||||
canvas.drawPath(_trianglePath(Offset(baseX, baseY + 2.0), width, height, false), paint);
|
||||
}
|
||||
|
||||
static Path _trianglePath(Offset o, double width, double height, bool isUp) {
|
||||
@@ -583,10 +519,7 @@ class ArrowClipper extends CustomClipper<Path> {
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2);
|
||||
path.lineTo(startPointX + arrowWidth, startPointY);
|
||||
path.lineTo(startPointX + arrowWidth, startPointY + 1.0);
|
||||
path.lineTo(
|
||||
startPointX + arrowWidth / 2,
|
||||
startPointY - arrowWidth / 2 + 1.0,
|
||||
);
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0);
|
||||
path.lineTo(startPointX, startPointY + 1.0);
|
||||
path.close();
|
||||
|
||||
@@ -595,10 +528,7 @@ class ArrowClipper extends CustomClipper<Path> {
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2);
|
||||
path.lineTo(startPointX, startPointY);
|
||||
path.lineTo(startPointX, startPointY - 1.0);
|
||||
path.lineTo(
|
||||
startPointX + arrowWidth / 2,
|
||||
startPointY + arrowWidth / 2 - 1.0,
|
||||
);
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0);
|
||||
path.lineTo(startPointX + arrowWidth, startPointY - 1.0);
|
||||
path.close();
|
||||
|
||||
@@ -613,11 +543,7 @@ class SlideFadeTransition extends StatelessWidget {
|
||||
final Animation<double> animation;
|
||||
final Widget child;
|
||||
|
||||
const SlideFadeTransition({
|
||||
super.key,
|
||||
required this.animation,
|
||||
required this.child,
|
||||
});
|
||||
const SlideFadeTransition({super.key, required this.animation, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -625,14 +551,8 @@ class SlideFadeTransition extends StatelessWidget {
|
||||
animation: animation,
|
||||
builder: (context, child) => animation.value == 0.0 ? const SizedBox() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0.3, 0.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(animation),
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
position: Tween(begin: const Offset(0.3, 0.0), end: const Offset(0.0, 0.0)).animate(animation),
|
||||
child: FadeTransition(opacity: animation, child: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@ import 'package:flutter/material.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
/// Build the Scroll Thumb and label using the current configuration
|
||||
typedef ScrollThumbBuilder = Widget Function(
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
Animation<double> labelAnimation,
|
||||
double height, {
|
||||
Text? labelText,
|
||||
BoxConstraints? labelConstraints,
|
||||
});
|
||||
typedef ScrollThumbBuilder =
|
||||
Widget Function(
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
Animation<double> labelAnimation,
|
||||
double height, {
|
||||
Text? labelText,
|
||||
BoxConstraints? labelConstraints,
|
||||
});
|
||||
|
||||
/// Build a Text widget using the current scroll offset
|
||||
typedef LabelTextBuilder = Text Function(int item);
|
||||
@@ -75,12 +76,8 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
|
||||
this.labelTextBuilder,
|
||||
this.labelConstraints,
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbSemicircleBuilder(
|
||||
heightScrollThumb * 0.6,
|
||||
scrollThumbKey,
|
||||
alwaysVisibleScrollThumb,
|
||||
);
|
||||
}) : assert(child.scrollDirection == Axis.vertical),
|
||||
scrollThumbBuilder = _thumbSemicircleBuilder(heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb);
|
||||
|
||||
@override
|
||||
DraggableScrollbarState createState() => DraggableScrollbarState();
|
||||
@@ -113,17 +110,10 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
if (alwaysVisibleScrollThumb) {
|
||||
return scrollThumbAndLabel;
|
||||
}
|
||||
return SlideFadeTransition(
|
||||
animation: thumbAnimation!,
|
||||
child: scrollThumbAndLabel,
|
||||
);
|
||||
return SlideFadeTransition(animation: thumbAnimation!, child: scrollThumbAndLabel);
|
||||
}
|
||||
|
||||
static ScrollThumbBuilder _thumbSemicircleBuilder(
|
||||
double width,
|
||||
Key? scrollThumbKey,
|
||||
bool alwaysVisibleScrollThumb,
|
||||
) {
|
||||
static ScrollThumbBuilder _thumbSemicircleBuilder(double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
|
||||
return (
|
||||
Color backgroundColor,
|
||||
Animation<double> thumbAnimation,
|
||||
@@ -144,9 +134,7 @@ class DraggableScrollbar extends StatefulWidget {
|
||||
topRight: const Radius.circular(4.0),
|
||||
bottomRight: const Radius.circular(4.0),
|
||||
),
|
||||
child: Container(
|
||||
constraints: BoxConstraints.tight(Size(width, height)),
|
||||
),
|
||||
child: Container(constraints: BoxConstraints.tight(Size(width, height))),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -219,25 +207,13 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
_isDragInProcess = false;
|
||||
_currentItem = 0;
|
||||
|
||||
_thumbAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.scrollbarAnimationDuration,
|
||||
);
|
||||
_thumbAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration);
|
||||
|
||||
_thumbAnimation = CurvedAnimation(
|
||||
parent: _thumbAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
_thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastOutSlowIn);
|
||||
|
||||
_labelAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.scrollbarAnimationDuration,
|
||||
);
|
||||
_labelAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration);
|
||||
|
||||
_labelAnimation = CurvedAnimation(
|
||||
parent: _labelAnimationController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
);
|
||||
_labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -272,9 +248,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
},
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
RepaintBoundary(
|
||||
child: widget.child,
|
||||
),
|
||||
RepaintBoundary(child: widget.child),
|
||||
RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
onVerticalDragStart: _onVerticalDragStart,
|
||||
@@ -370,16 +344,12 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
/// If the bar is at the bottom but the item position is still smaller than the max item count (due to rounding error)
|
||||
/// jump to the end of the list
|
||||
if (barMaxScrollExtent - _barOffset < 10 && itemPosition < maxItemCount) {
|
||||
widget.controller.jumpTo(
|
||||
index: maxItemCount,
|
||||
);
|
||||
widget.controller.jumpTo(index: maxItemCount);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
widget.controller.jumpTo(
|
||||
index: itemPosition,
|
||||
);
|
||||
widget.controller.jumpTo(index: itemPosition);
|
||||
}
|
||||
|
||||
Timer? dragHaltTimer;
|
||||
@@ -405,12 +375,9 @@ class DraggableScrollbarState extends State<DraggableScrollbar> with TickerProvi
|
||||
dragHaltTimer?.cancel();
|
||||
widget.scrollStateListener(true);
|
||||
|
||||
dragHaltTimer = Timer(
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
widget.scrollStateListener(false);
|
||||
},
|
||||
);
|
||||
dragHaltTimer = Timer(const Duration(milliseconds: 500), () {
|
||||
widget.scrollStateListener(false);
|
||||
});
|
||||
}
|
||||
|
||||
_jumpToBarPosition();
|
||||
@@ -451,14 +418,8 @@ class ArrowCustomPainter extends CustomPainter {
|
||||
final baseX = size.width / 2;
|
||||
final baseY = size.height / 2;
|
||||
|
||||
canvas.drawPath(
|
||||
_trianglePath(Offset(baseX, baseY - 2.0), width, height, true),
|
||||
paint,
|
||||
);
|
||||
canvas.drawPath(
|
||||
_trianglePath(Offset(baseX, baseY + 2.0), width, height, false),
|
||||
paint,
|
||||
);
|
||||
canvas.drawPath(_trianglePath(Offset(baseX, baseY - 2.0), width, height, true), paint);
|
||||
canvas.drawPath(_trianglePath(Offset(baseX, baseY + 2.0), width, height, false), paint);
|
||||
}
|
||||
|
||||
static Path _trianglePath(Offset o, double width, double height, bool isUp) {
|
||||
@@ -489,10 +450,7 @@ class ArrowClipper extends CustomClipper<Path> {
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2);
|
||||
path.lineTo(startPointX + arrowWidth, startPointY);
|
||||
path.lineTo(startPointX + arrowWidth, startPointY + 1.0);
|
||||
path.lineTo(
|
||||
startPointX + arrowWidth / 2,
|
||||
startPointY - arrowWidth / 2 + 1.0,
|
||||
);
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0);
|
||||
path.lineTo(startPointX, startPointY + 1.0);
|
||||
path.close();
|
||||
|
||||
@@ -501,10 +459,7 @@ class ArrowClipper extends CustomClipper<Path> {
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2);
|
||||
path.lineTo(startPointX, startPointY);
|
||||
path.lineTo(startPointX, startPointY - 1.0);
|
||||
path.lineTo(
|
||||
startPointX + arrowWidth / 2,
|
||||
startPointY + arrowWidth / 2 - 1.0,
|
||||
);
|
||||
path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0);
|
||||
path.lineTo(startPointX + arrowWidth, startPointY - 1.0);
|
||||
path.close();
|
||||
|
||||
@@ -519,11 +474,7 @@ class SlideFadeTransition extends StatelessWidget {
|
||||
final Animation<double> animation;
|
||||
final Widget child;
|
||||
|
||||
const SlideFadeTransition({
|
||||
super.key,
|
||||
required this.animation,
|
||||
required this.child,
|
||||
});
|
||||
const SlideFadeTransition({super.key, required this.animation, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -531,14 +482,8 @@ class SlideFadeTransition extends StatelessWidget {
|
||||
animation: animation,
|
||||
builder: (context, child) => animation.value == 0.0 ? const SizedBox() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0.3, 0.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(animation),
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
position: Tween(begin: const Offset(0.3, 0.0), end: const Offset(0.0, 0.0)).animate(animation),
|
||||
child: FadeTransition(opacity: animation, child: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,13 +30,10 @@ class GroupDividerTitle extends HookConsumerWidget {
|
||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||
final groupBy = useState(GroupAssetsBy.day);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
groupBy.value = GroupAssetsBy.values[appSettingService.getSetting<int>(AppSettingsEnum.groupAssetsBy)];
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
useEffect(() {
|
||||
groupBy.value = GroupAssetsBy.values[appSettingService.getSetting<int>(AppSettingsEnum.groupAssetsBy)];
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
void handleTitleIconClick() {
|
||||
ref.read(hapticFeedbackProvider.notifier).heavyImpact();
|
||||
@@ -59,9 +56,7 @@ class GroupDividerTitle extends HookConsumerWidget {
|
||||
Text(
|
||||
text,
|
||||
style: groupBy.value == GroupAssetsBy.month
|
||||
? context.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 24.0,
|
||||
)
|
||||
? context.textTheme.bodyLarge?.copyWith(fontSize: 24.0)
|
||||
: context.textTheme.labelLarge?.copyWith(
|
||||
color: context.textTheme.labelLarge?.color?.withAlpha(250),
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -60,9 +60,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
var settings = ref.watch(appSettingsServiceProvider);
|
||||
|
||||
final perRow = useState(
|
||||
assetsPerRow ?? settings.getSetting(AppSettingsEnum.tilesPerRow)!,
|
||||
);
|
||||
final perRow = useState(assetsPerRow ?? settings.getSetting(AppSettingsEnum.tilesPerRow)!);
|
||||
final scaleFactor = useState(7.0 - perRow.value);
|
||||
final baseScaleFactor = useState(7.0 - perRow.value);
|
||||
|
||||
@@ -82,22 +80,21 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
||||
return RawGestureDetector(
|
||||
gestures: {
|
||||
CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers<CustomScaleGestureRecognizer>(
|
||||
() => CustomScaleGestureRecognizer(), (CustomScaleGestureRecognizer scale) {
|
||||
scale.onStart = (details) {
|
||||
baseScaleFactor.value = scaleFactor.value;
|
||||
};
|
||||
() => CustomScaleGestureRecognizer(),
|
||||
(CustomScaleGestureRecognizer scale) {
|
||||
scale.onStart = (details) {
|
||||
baseScaleFactor.value = scaleFactor.value;
|
||||
};
|
||||
|
||||
scale.onUpdate = (details) {
|
||||
scaleFactor.value = max(
|
||||
min(5.0, baseScaleFactor.value * details.scale),
|
||||
1.0,
|
||||
);
|
||||
if (7 - scaleFactor.value.toInt() != perRow.value) {
|
||||
perRow.value = 7 - scaleFactor.value.toInt();
|
||||
settings.setSetting(AppSettingsEnum.tilesPerRow, perRow.value);
|
||||
}
|
||||
};
|
||||
}),
|
||||
scale.onUpdate = (details) {
|
||||
scaleFactor.value = max(min(5.0, baseScaleFactor.value * details.scale), 1.0);
|
||||
if (7 - scaleFactor.value.toInt() != perRow.value) {
|
||||
perRow.value = 7 - scaleFactor.value.toInt();
|
||||
settings.setSetting(AppSettingsEnum.tilesPerRow, perRow.value);
|
||||
}
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
child: ImmichAssetGridView(
|
||||
onRefresh: onRefresh,
|
||||
@@ -125,9 +122,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
||||
if (renderList != null) return buildAssetGridView(renderList!);
|
||||
|
||||
final renderListFuture = ref.watch(assetsTimelineProvider(assets!));
|
||||
return renderListFuture.widgetWhen(
|
||||
onData: (renderList) => buildAssetGridView(renderList),
|
||||
);
|
||||
return renderListFuture.widgetWhen(onData: (renderList) => buildAssetGridView(renderList));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@ import 'disable_multi_select_button.dart';
|
||||
import 'draggable_scrollbar_custom.dart';
|
||||
import 'group_divider_title.dart';
|
||||
|
||||
typedef ImmichAssetGridSelectionListener = void Function(
|
||||
bool,
|
||||
Set<Asset>,
|
||||
);
|
||||
typedef ImmichAssetGridSelectionListener = void Function(bool, Set<Asset>);
|
||||
|
||||
class ImmichAssetGridView extends ConsumerStatefulWidget {
|
||||
final RenderList renderList;
|
||||
@@ -161,16 +158,10 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
// the scroll_position widget crashes. This is a workaround to prevent this.
|
||||
// If the index is within the last 10 elements, we jump instead of scrolling.
|
||||
if (widget.renderList.elements.length <= index + 10) {
|
||||
_itemScrollController.jumpTo(
|
||||
index: index,
|
||||
);
|
||||
_itemScrollController.jumpTo(index: index);
|
||||
return;
|
||||
}
|
||||
await _itemScrollController.scrollTo(
|
||||
index: index,
|
||||
alignment: 0,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
);
|
||||
await _itemScrollController.scrollTo(index: index, alignment: 0, duration: const Duration(milliseconds: 500));
|
||||
}
|
||||
|
||||
Widget _itemBuilder(BuildContext c, int position) {
|
||||
@@ -219,18 +210,12 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
|
||||
return Text(
|
||||
DateFormat.yMMMM().format(date),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMultiSelectIndicator() {
|
||||
return DisableMultiSelectButton(
|
||||
onPressed: () => _deselectAll(),
|
||||
selectedItemCount: _selectedAssets.length,
|
||||
);
|
||||
return DisableMultiSelectButton(onPressed: () => _deselectAll(), selectedItemCount: _selectedAssets.length);
|
||||
}
|
||||
|
||||
Widget _buildAssetGrid() {
|
||||
@@ -250,10 +235,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
}
|
||||
|
||||
final listWidget = ScrollablePositionedList.builder(
|
||||
padding: EdgeInsets.only(
|
||||
top: appBarOffset() ? 60 : 0,
|
||||
bottom: 220,
|
||||
),
|
||||
padding: EdgeInsets.only(top: appBarOffset() ? 60 : 0, bottom: 220),
|
||||
itemBuilder: _itemBuilder,
|
||||
itemPositionsListener: _itemPositionsListener,
|
||||
physics: _scrollPhysics,
|
||||
@@ -269,8 +251,9 @@ 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(),
|
||||
heightOffset: appBarOffset() ? 60 : 0,
|
||||
@@ -284,12 +267,8 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
return widget.onRefresh == null
|
||||
? child
|
||||
: appBarOffset()
|
||||
? RefreshIndicator(
|
||||
onRefresh: widget.onRefresh!,
|
||||
edgeOffset: 30,
|
||||
child: child,
|
||||
)
|
||||
: RefreshIndicator(onRefresh: widget.onRefresh!, child: child);
|
||||
? RefreshIndicator(onRefresh: widget.onRefresh!, edgeOffset: 30, child: child)
|
||||
: RefreshIndicator(onRefresh: widget.onRefresh!, child: child);
|
||||
}
|
||||
|
||||
void _scrollToDate() {
|
||||
@@ -312,9 +291,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
// If the exact date is not found, the timeline is grouped by month,
|
||||
// thus we search for the month
|
||||
if (index == -1) {
|
||||
index = widget.renderList.elements.indexWhere(
|
||||
(e) => e.date.year == date.year && e.date.month == date.month,
|
||||
);
|
||||
index = widget.renderList.elements.indexWhere((e) => e.date.year == date.year && e.date.month == date.month);
|
||||
}
|
||||
|
||||
if (index < widget.renderList.elements.length) {
|
||||
@@ -411,13 +388,8 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
void _scrollToTop() {
|
||||
// for some reason, this is necessary as well in order
|
||||
// to correctly reposition the drag thumb scroll bar
|
||||
_itemScrollController.jumpTo(
|
||||
index: 0,
|
||||
);
|
||||
_itemScrollController.scrollTo(
|
||||
index: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
_itemScrollController.jumpTo(index: 0);
|
||||
_itemScrollController.scrollTo(index: 0, duration: const Duration(milliseconds: 200));
|
||||
}
|
||||
|
||||
void _setDragStartIndex(AssetIndex index) {
|
||||
@@ -495,9 +467,7 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
final sectionAssets = widget.renderList.loadAssets(section.offset, section.count);
|
||||
|
||||
if (currentSectionIndex == startSectionIndex) {
|
||||
selectedAssets.addAll(
|
||||
sectionAssets.slice(startSectionAssetIndex, sectionAssets.length),
|
||||
);
|
||||
selectedAssets.addAll(sectionAssets.slice(startSectionAssetIndex, sectionAssets.length));
|
||||
} else {
|
||||
selectedAssets.addAll(sectionAssets);
|
||||
}
|
||||
@@ -509,13 +479,9 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
if (section != null) {
|
||||
final sectionAssets = widget.renderList.loadAssets(section.offset, section.count);
|
||||
if (startSectionIndex == endSectionIndex) {
|
||||
selectedAssets.addAll(
|
||||
sectionAssets.slice(startSectionAssetIndex, endSectionAssetIndex + 1),
|
||||
);
|
||||
selectedAssets.addAll(sectionAssets.slice(startSectionAssetIndex, endSectionAssetIndex + 1));
|
||||
} else {
|
||||
selectedAssets.addAll(
|
||||
sectionAssets.slice(0, endSectionAssetIndex + 1),
|
||||
);
|
||||
selectedAssets.addAll(sectionAssets.slice(0, endSectionAssetIndex + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,9 +520,8 @@ class ImmichAssetGridViewState extends ConsumerState<ImmichAssetGridView> {
|
||||
onAssetEnter: _handleDragAssetEnter,
|
||||
onEnd: _stopDrag,
|
||||
onScroll: _dragDragScroll,
|
||||
onScrollStart: () => WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => controlBottomAppBarNotifier.minimize(),
|
||||
),
|
||||
onScrollStart: () =>
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => controlBottomAppBarNotifier.minimize()),
|
||||
child: _buildAssetGrid(),
|
||||
),
|
||||
if (widget.showMultiSelectIndicator && widget.selectionActive) _buildMultiSelectIndicator(),
|
||||
@@ -590,10 +555,7 @@ class _PlaceholderRow extends StatelessWidget {
|
||||
key: ValueKey(i),
|
||||
width: width,
|
||||
height: height,
|
||||
margin: EdgeInsets.only(
|
||||
bottom: margin,
|
||||
right: i + 1 == number ? 0.0 : margin,
|
||||
),
|
||||
margin: EdgeInsets.only(bottom: margin, right: i + 1 == number ? 0.0 : margin),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -639,9 +601,7 @@ class _Section extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth / assetsPerRow - margin * (assetsPerRow - 1) / assetsPerRow;
|
||||
@@ -675,10 +635,7 @@ class _Section extends StatelessWidget {
|
||||
key: ValueKey(i),
|
||||
rowStartIndex: i * assetsPerRow,
|
||||
sectionIndex: sectionIndex,
|
||||
assets: assetsToRender.nestedSlice(
|
||||
i * assetsPerRow,
|
||||
min((i + 1) * assetsPerRow, section.count),
|
||||
),
|
||||
assets: assetsToRender.nestedSlice(i * assetsPerRow, min((i + 1) * assetsPerRow, section.count)),
|
||||
absoluteOffset: section.offset + i * assetsPerRow,
|
||||
width: width,
|
||||
assetsPerRow: assetsPerRow,
|
||||
@@ -706,9 +663,7 @@ class _Section extends StatelessWidget {
|
||||
class _MonthTitle extends StatelessWidget {
|
||||
final DateTime date;
|
||||
|
||||
const _MonthTitle({
|
||||
required this.date,
|
||||
});
|
||||
const _MonthTitle({required this.date});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -719,10 +674,7 @@ class _MonthTitle extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(left: 12.0, top: 24.0),
|
||||
child: Text(
|
||||
toBeginningOfSentenceCase(title, context.locale.languageCode),
|
||||
style: const TextStyle(
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: const TextStyle(fontSize: 26, fontWeight: FontWeight.w500),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -821,11 +773,7 @@ class _AssetRow extends StatelessWidget {
|
||||
|
||||
// Normalize:
|
||||
final sum = arConfiguration.sum;
|
||||
widthDistribution.setRange(
|
||||
0,
|
||||
widthDistribution.length,
|
||||
arConfiguration.map((e) => (e * assets.length) / sum),
|
||||
);
|
||||
widthDistribution.setRange(0, widthDistribution.length, arConfiguration.map((e) => (e * assets.length) / sum));
|
||||
}
|
||||
return Row(
|
||||
key: key,
|
||||
@@ -835,10 +783,7 @@ class _AssetRow extends StatelessWidget {
|
||||
return Container(
|
||||
width: width * widthDistribution[index],
|
||||
height: width,
|
||||
margin: EdgeInsets.only(
|
||||
bottom: margin,
|
||||
right: last ? 0.0 : margin,
|
||||
),
|
||||
margin: EdgeInsets.only(bottom: margin, right: last ? 0.0 : margin),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (selectionActive) {
|
||||
|
||||
@@ -79,53 +79,38 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
final currentUser = ref.watch(currentUserProvider);
|
||||
final processing = useProcessingOverlay();
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
selectionEnabledHook.addListener(() {
|
||||
multiselectEnabled.state = selectionEnabledHook.value;
|
||||
});
|
||||
useEffect(() {
|
||||
selectionEnabledHook.addListener(() {
|
||||
multiselectEnabled.state = selectionEnabledHook.value;
|
||||
});
|
||||
|
||||
return () {
|
||||
// This does not work in tests
|
||||
if (kReleaseMode) {
|
||||
selectionEnabledHook.dispose();
|
||||
}
|
||||
};
|
||||
},
|
||||
[],
|
||||
);
|
||||
return () {
|
||||
// This does not work in tests
|
||||
if (kReleaseMode) {
|
||||
selectionEnabledHook.dispose();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
void selectionListener(
|
||||
bool multiselect,
|
||||
Set<Asset> selectedAssets,
|
||||
) {
|
||||
void selectionListener(bool multiselect, Set<Asset> selectedAssets) {
|
||||
selectionEnabledHook.value = multiselect;
|
||||
selection.value = selectedAssets;
|
||||
selectionAssetState.value = AssetSelectionState.fromSelection(selectedAssets);
|
||||
}
|
||||
|
||||
errorBuilder(String? msg) => msg != null && msg.isNotEmpty
|
||||
? () => ImmichToast.show(
|
||||
context: context,
|
||||
msg: msg,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
)
|
||||
? () => ImmichToast.show(context: context, msg: msg, gravity: ToastGravity.BOTTOM)
|
||||
: null;
|
||||
|
||||
Iterable<Asset> ownedRemoteSelection({
|
||||
String? localErrorMessage,
|
||||
String? ownerErrorMessage,
|
||||
}) {
|
||||
Iterable<Asset> ownedRemoteSelection({String? localErrorMessage, String? ownerErrorMessage}) {
|
||||
final assets = selection.value;
|
||||
return assets.remoteOnly(errorCallback: errorBuilder(localErrorMessage)).ownedOnly(
|
||||
currentUser,
|
||||
errorCallback: errorBuilder(ownerErrorMessage),
|
||||
);
|
||||
return assets
|
||||
.remoteOnly(errorCallback: errorBuilder(localErrorMessage))
|
||||
.ownedOnly(currentUser, errorCallback: errorBuilder(ownerErrorMessage));
|
||||
}
|
||||
|
||||
Iterable<Asset> remoteSelection({String? errorMessage}) => selection.value.remoteOnly(
|
||||
errorCallback: errorBuilder(errorMessage),
|
||||
);
|
||||
Iterable<Asset> remoteSelection({String? errorMessage}) =>
|
||||
selection.value.remoteOnly(errorCallback: errorBuilder(errorMessage));
|
||||
|
||||
void onShareAssets(bool shareLocal) {
|
||||
processing.value = true;
|
||||
@@ -174,10 +159,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
processing.value = true;
|
||||
try {
|
||||
final toDelete = selection.value
|
||||
.ownedOnly(
|
||||
currentUser,
|
||||
errorCallback: errorBuilder('home_page_delete_err_partner'.tr()),
|
||||
)
|
||||
.ownedOnly(currentUser, errorCallback: errorBuilder('home_page_delete_err_partner'.tr()))
|
||||
.toList();
|
||||
final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(toDelete, force: force);
|
||||
|
||||
@@ -237,25 +219,10 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
final failedCount = totalCount - successCount;
|
||||
|
||||
final msg = failedCount > 0
|
||||
? 'assets_downloaded_failed'.t(
|
||||
context: context,
|
||||
args: {
|
||||
'count': successCount,
|
||||
'error': failedCount,
|
||||
},
|
||||
)
|
||||
: 'assets_downloaded_successfully'.t(
|
||||
context: context,
|
||||
args: {
|
||||
'count': successCount,
|
||||
},
|
||||
);
|
||||
? 'assets_downloaded_failed'.t(context: context, args: {'count': successCount, 'error': failedCount})
|
||||
: 'assets_downloaded_successfully'.t(context: context, args: {'count': successCount});
|
||||
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: msg,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
ImmichToast.show(context: context, msg: msg, gravity: ToastGravity.BOTTOM);
|
||||
} finally {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
@@ -270,10 +237,9 @@ 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,
|
||||
@@ -293,10 +259,9 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
processing.value = true;
|
||||
selectionEnabledHook.value = false;
|
||||
try {
|
||||
ref.read(manualUploadProvider.notifier).uploadAssets(
|
||||
context,
|
||||
selection.value.where((a) => a.storage == AssetState.local),
|
||||
);
|
||||
ref
|
||||
.read(manualUploadProvider.notifier)
|
||||
.uploadAssets(context, selection.value.where((a) => a.storage == AssetState.local));
|
||||
} finally {
|
||||
processing.value = false;
|
||||
}
|
||||
@@ -305,16 +270,11 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
void onAddToAlbum(Album album) async {
|
||||
processing.value = true;
|
||||
try {
|
||||
final Iterable<Asset> assets = remoteSelection(
|
||||
errorMessage: "home_page_add_to_album_err_local".tr(),
|
||||
);
|
||||
final Iterable<Asset> assets = remoteSelection(errorMessage: "home_page_add_to_album_err_local".tr());
|
||||
if (assets.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final result = await ref.read(albumServiceProvider).addAssets(
|
||||
album,
|
||||
assets,
|
||||
);
|
||||
final result = await ref.read(albumServiceProvider).addAssets(album, assets);
|
||||
|
||||
if (result != null) {
|
||||
if (result.alreadyInAlbum.isNotEmpty) {
|
||||
@@ -332,10 +292,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "home_page_add_to_album_success".tr(
|
||||
namedArgs: {
|
||||
"album": album.name,
|
||||
"added": result.successfullyAdded.toString(),
|
||||
},
|
||||
namedArgs: {"album": album.name, "added": result.successfullyAdded.toString()},
|
||||
),
|
||||
toastType: ToastType.success,
|
||||
);
|
||||
@@ -350,9 +307,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
void onCreateNewAlbum() async {
|
||||
processing.value = true;
|
||||
try {
|
||||
final Iterable<Asset> assets = remoteSelection(
|
||||
errorMessage: "home_page_add_to_album_err_local".tr(),
|
||||
);
|
||||
final Iterable<Asset> assets = remoteSelection(errorMessage: "home_page_add_to_album_err_local".tr());
|
||||
if (assets.isEmpty) {
|
||||
return;
|
||||
}
|
||||
@@ -376,9 +331,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
await ref.read(stackServiceProvider).createStack(
|
||||
selection.value.map((e) => e.remoteId!).toList(),
|
||||
);
|
||||
await ref.read(stackServiceProvider).createStack(selection.value.map((e) => e.remoteId!).toList());
|
||||
} finally {
|
||||
processing.value = false;
|
||||
selectionEnabledHook.value = false;
|
||||
@@ -426,12 +379,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
final isInLockedView = ref.read(inLockedViewProvider);
|
||||
final visibility = isInLockedView ? AssetVisibilityEnum.timeline : AssetVisibilityEnum.locked;
|
||||
|
||||
await handleSetAssetsVisibility(
|
||||
ref,
|
||||
context,
|
||||
visibility,
|
||||
remoteAssets.toList(),
|
||||
);
|
||||
await handleSetAssetsVisibility(ref, context, visibility, remoteAssets.toList());
|
||||
}
|
||||
} finally {
|
||||
processing.value = false;
|
||||
@@ -439,41 +387,34 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Future<T> Function() wrapLongRunningFun<T>(
|
||||
Future<T> Function() fun, {
|
||||
bool showOverlay = true,
|
||||
}) =>
|
||||
() async {
|
||||
if (showOverlay) processing.value = true;
|
||||
try {
|
||||
final result = await fun();
|
||||
if (result.runtimeType != bool || result == true) {
|
||||
selectionEnabledHook.value = false;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
if (showOverlay) processing.value = false;
|
||||
}
|
||||
};
|
||||
Future<T> Function() wrapLongRunningFun<T>(Future<T> Function() fun, {bool showOverlay = true}) => () async {
|
||||
if (showOverlay) processing.value = true;
|
||||
try {
|
||||
final result = await fun();
|
||||
if (result.runtimeType != bool || result == true) {
|
||||
selectionEnabledHook.value = false;
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
if (showOverlay) processing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
return SafeArea(
|
||||
top: true,
|
||||
bottom: false,
|
||||
child: Stack(
|
||||
children: [
|
||||
ref.watch(renderListProvider).when(
|
||||
ref
|
||||
.watch(renderListProvider)
|
||||
.when(
|
||||
data: (data) => data.isEmpty && (buildLoadingIndicator != null || topWidget == null)
|
||||
? (buildLoadingIndicator ?? buildEmptyIndicator)()
|
||||
: ImmichAssetGrid(
|
||||
renderList: data,
|
||||
listener: selectionListener,
|
||||
selectionActive: selectionEnabledHook.value,
|
||||
onRefresh: onRefresh == null
|
||||
? null
|
||||
: wrapLongRunningFun(
|
||||
onRefresh!,
|
||||
showOverlay: false,
|
||||
),
|
||||
onRefresh: onRefresh == null ? null : wrapLongRunningFun(onRefresh!, showOverlay: false),
|
||||
topWidget: topWidget,
|
||||
showStack: stackEnabled,
|
||||
showDragScrollLabel: dragScrollLabelEnabled,
|
||||
@@ -506,9 +447,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||
unarchive: unarchive,
|
||||
onToggleLocked: onToggleLockedVisibility,
|
||||
onRemoveFromAlbum: onRemoveFromAlbum != null
|
||||
? wrapLongRunningFun(
|
||||
() => onRemoveFromAlbum!(selection.value),
|
||||
)
|
||||
? wrapLongRunningFun(() => onRemoveFromAlbum!(selection.value))
|
||||
: null,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -5,11 +5,7 @@ import 'package:immich_mobile/providers/asset_viewer/render_list_status_provider
|
||||
import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart';
|
||||
|
||||
class MultiselectGridStatusIndicator extends HookConsumerWidget {
|
||||
const MultiselectGridStatusIndicator({
|
||||
super.key,
|
||||
this.buildLoadingIndicator,
|
||||
this.emptyIndicator,
|
||||
});
|
||||
const MultiselectGridStatusIndicator({super.key, this.buildLoadingIndicator, this.emptyIndicator});
|
||||
|
||||
final Widget Function()? buildLoadingIndicator;
|
||||
final Widget? emptyIndicator;
|
||||
@@ -18,16 +14,13 @@ class MultiselectGridStatusIndicator extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final renderListStatus = ref.watch(renderListStatusProvider);
|
||||
return switch (renderListStatus) {
|
||||
RenderListStatusEnum.loading => buildLoadingIndicator == null
|
||||
? const Center(
|
||||
child: DelayedLoadingIndicator(
|
||||
delay: Duration(milliseconds: 500),
|
||||
),
|
||||
)
|
||||
: buildLoadingIndicator!(),
|
||||
RenderListStatusEnum.loading =>
|
||||
buildLoadingIndicator == null
|
||||
? const Center(child: DelayedLoadingIndicator(delay: Duration(milliseconds: 500)))
|
||||
: buildLoadingIndicator!(),
|
||||
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()
|
||||
RenderListStatusEnum.complete => const SizedBox(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,9 @@ 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: [
|
||||
@@ -52,16 +53,13 @@ class ThumbnailImage extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
border: multiselectEnabled && isSelected
|
||||
? canDeselect
|
||||
? Border.all(
|
||||
color: assetContainerColor,
|
||||
width: 8,
|
||||
)
|
||||
: const Border(
|
||||
top: BorderSide(color: Colors.grey, width: 8),
|
||||
right: BorderSide(color: Colors.grey, width: 8),
|
||||
bottom: BorderSide(color: Colors.grey, width: 8),
|
||||
left: BorderSide(color: Colors.grey, width: 8),
|
||||
)
|
||||
? Border.all(color: assetContainerColor, width: 8)
|
||||
: const Border(
|
||||
top: BorderSide(color: Colors.grey, width: 8),
|
||||
right: BorderSide(color: Colors.grey, width: 8),
|
||||
bottom: BorderSide(color: Colors.grey, width: 8),
|
||||
left: BorderSide(color: Colors.grey, width: 8),
|
||||
)
|
||||
: const Border(),
|
||||
),
|
||||
child: Stack(
|
||||
@@ -76,21 +74,9 @@ class ThumbnailImage extends StatelessWidget {
|
||||
),
|
||||
if (showStorageIndicator) _StorageIcon(storage: asset.storage),
|
||||
if (asset.isFavorite)
|
||||
const Positioned(
|
||||
left: 8,
|
||||
bottom: 5,
|
||||
child: Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
const Positioned(left: 8, bottom: 5, child: Icon(Icons.favorite, color: Colors.white, size: 16)),
|
||||
if (asset.isVideo) _VideoIcon(duration: asset.duration),
|
||||
if (asset.stackCount > 0)
|
||||
_StackIcon(
|
||||
isVideo: asset.isVideo,
|
||||
stackCount: asset.stackCount,
|
||||
),
|
||||
if (asset.stackCount > 0) _StackIcon(isVideo: asset.isVideo, stackCount: asset.stackCount),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -98,15 +84,9 @@ class ThumbnailImage extends StatelessWidget {
|
||||
isSelected
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(3.0),
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: _SelectedIcon(),
|
||||
),
|
||||
child: Align(alignment: Alignment.topLeft, child: _SelectedIcon()),
|
||||
)
|
||||
: const Icon(
|
||||
Icons.circle_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
: const Icon(Icons.circle_outlined, color: Colors.white),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -117,18 +97,13 @@ 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(
|
||||
shape: BoxShape.circle,
|
||||
color: assetContainerColor,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: assetContainerColor),
|
||||
child: Icon(Icons.check_circle_rounded, color: context.primaryColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -147,18 +122,10 @@ class _VideoIcon extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
duration.format(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
const Icon(
|
||||
Icons.play_circle_fill_rounded,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
const Icon(Icons.play_circle_fill_rounded, color: Colors.white, size: 18),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -181,21 +148,10 @@ class _StackIcon extends StatelessWidget {
|
||||
if (stackCount > 1)
|
||||
Text(
|
||||
"$stackCount",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold),
|
||||
),
|
||||
if (stackCount > 1)
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
const Icon(
|
||||
Icons.burst_mode_rounded,
|
||||
color: Colors.white,
|
||||
size: 18,
|
||||
),
|
||||
if (stackCount > 1) const SizedBox(width: 3),
|
||||
const Icon(Icons.burst_mode_rounded, color: Colors.white, size: 18),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -211,53 +167,35 @@ class _StorageIcon extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return switch (storage) {
|
||||
AssetState.local => const Positioned(
|
||||
right: 8,
|
||||
bottom: 5,
|
||||
child: Icon(
|
||||
Icons.cloud_off_outlined,
|
||||
color: Color.fromRGBO(255, 255, 255, 0.8),
|
||||
size: 16,
|
||||
shadows: [
|
||||
Shadow(
|
||||
blurRadius: 5.0,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.6),
|
||||
offset: Offset(0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
right: 8,
|
||||
bottom: 5,
|
||||
child: Icon(
|
||||
Icons.cloud_off_outlined,
|
||||
color: Color.fromRGBO(255, 255, 255, 0.8),
|
||||
size: 16,
|
||||
shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))],
|
||||
),
|
||||
),
|
||||
AssetState.remote => const Positioned(
|
||||
right: 8,
|
||||
bottom: 5,
|
||||
child: Icon(
|
||||
Icons.cloud_outlined,
|
||||
color: Color.fromRGBO(255, 255, 255, 0.8),
|
||||
size: 16,
|
||||
shadows: [
|
||||
Shadow(
|
||||
blurRadius: 5.0,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.6),
|
||||
offset: Offset(0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
right: 8,
|
||||
bottom: 5,
|
||||
child: Icon(
|
||||
Icons.cloud_outlined,
|
||||
color: Color.fromRGBO(255, 255, 255, 0.8),
|
||||
size: 16,
|
||||
shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))],
|
||||
),
|
||||
),
|
||||
AssetState.merged => const Positioned(
|
||||
right: 8,
|
||||
bottom: 5,
|
||||
child: Icon(
|
||||
Icons.cloud_done_outlined,
|
||||
color: Color.fromRGBO(255, 255, 255, 0.8),
|
||||
size: 16,
|
||||
shadows: [
|
||||
Shadow(
|
||||
blurRadius: 5.0,
|
||||
color: Color.fromRGBO(0, 0, 0, 0.6),
|
||||
offset: Offset(0.0, 0.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
right: 8,
|
||||
bottom: 5,
|
||||
child: Icon(
|
||||
Icons.cloud_done_outlined,
|
||||
color: Color.fromRGBO(255, 255, 255, 0.8),
|
||||
size: 16,
|
||||
shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))],
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -288,13 +226,7 @@ class _ImageIcon extends StatelessWidget {
|
||||
tag: isDto ? '${asset.remoteId}-$heroOffset' : asset.id + heroOffset,
|
||||
child: Stack(
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: ImmichThumbnail(
|
||||
asset: asset,
|
||||
height: 250,
|
||||
width: 250,
|
||||
),
|
||||
),
|
||||
SizedBox.expand(child: ImmichThumbnail(asset: asset, height: 250, width: 250)),
|
||||
const DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
@@ -321,10 +253,7 @@ class _ImageIcon extends StatelessWidget {
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: canDeselect ? BoxDecoration(color: assetContainerColor) : const BoxDecoration(color: Colors.grey),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15.0)),
|
||||
child: image,
|
||||
),
|
||||
child: ClipRRect(borderRadius: const BorderRadius.all(Radius.circular(15.0)), child: image),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,7 @@ class ThumbnailPlaceholder extends StatelessWidget {
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const ThumbnailPlaceholder({
|
||||
super.key,
|
||||
this.margin = EdgeInsets.zero,
|
||||
this.width = 250,
|
||||
this.height = 250,
|
||||
});
|
||||
const ThumbnailPlaceholder({super.key, this.margin = EdgeInsets.zero, this.width = 250, this.height = 250});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -26,11 +21,7 @@ class ThumbnailPlaceholder extends StatelessWidget {
|
||||
height: height,
|
||||
margin: margin,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: gradientColors,
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
gradient: LinearGradient(colors: gradientColors, begin: Alignment.topCenter, end: Alignment.bottomCenter),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ class UploadDialog extends ConfirmDialog {
|
||||
final Function onUpload;
|
||||
|
||||
const UploadDialog({super.key, required this.onUpload})
|
||||
: super(
|
||||
title: 'upload_dialog_title',
|
||||
content: 'upload_dialog_info',
|
||||
cancel: 'cancel',
|
||||
ok: 'upload',
|
||||
onOk: onUpload,
|
||||
);
|
||||
: super(
|
||||
title: 'upload_dialog_title',
|
||||
content: 'upload_dialog_info',
|
||||
cancel: 'cancel',
|
||||
ok: 'upload',
|
||||
onOk: onUpload,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user