chore: bump dart sdk to 3.8 (#20355)

* chore: bump dart sdk to 3.8

* chore: make build

* make pigeon

* chore: format files

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2025-07-29 00:34:03 +05:30
committed by GitHub
parent 9b3718120b
commit e52b9d15b5
643 changed files with 32561 additions and 35292 deletions

View File

@@ -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) {

View File

@@ -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)) {

View File

@@ -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(),
),
],

View File

@@ -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(),
),
],

View File

@@ -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),
),
),
),

View File

@@ -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),
),
);
}

View File

@@ -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),
),
);
}

View File

@@ -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,

View File

@@ -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));
}
}

View File

@@ -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) {

View File

@@ -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,
),
],

View File

@@ -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(),
};
}
}

View File

@@ -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),
);
}
}

View File

@@ -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),
),
);
}

View File

@@ -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,
);
}