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
+1 -5
View File
@@ -60,11 +60,7 @@ class ThrottleProgressUpdate {
int progress = 0;
int total = 0;
void call({
final String? title,
final int progress = 0,
final int total = 0,
}) {
void call({final String? title, final int progress = 0, final int total = 0}) {
final time = Timeline.now;
this.title = title ?? this.title;
this.progress = progress;
+1 -4
View File
@@ -48,10 +48,7 @@ abstract final class Bootstrap {
);
}
static Future<void> initDomain(
Isar db, {
bool shouldBufferLogs = true,
}) async {
static Future<void> initDomain(Isar db, {bool shouldBufferLogs = true}) async {
await StoreService.init(storeRepository: IsarStoreRepository(db));
await LogService.init(
logRepository: IsarLogRepository(db),
+3 -3
View File
@@ -39,7 +39,8 @@ final class CustomImageCache implements ImageCache {
/// Gets the cache for the given key
/// [_large] is used for [ImmichLocalImageProvider] and [ImmichRemoteImageProvider]
/// [_small] is used for [ImmichLocalThumbnailProvider] and [ImmichRemoteThumbnailProvider]
ImageCache _cacheForKey(Object key) => (key is ImmichLocalImageProvider ||
ImageCache _cacheForKey(Object key) =>
(key is ImmichLocalImageProvider ||
key is ImmichRemoteImageProvider ||
key is LocalFullImageProvider ||
key is RemoteFullImageProvider)
@@ -73,8 +74,7 @@ final class CustomImageCache implements ImageCache {
Object key,
ImageStreamCompleter Function() loader, {
ImageErrorListener? onError,
}) =>
_cacheForKey(key).putIfAbsent(key, loader, onError: onError);
}) => _cacheForKey(key).putIfAbsent(key, loader, onError: onError);
@override
ImageCacheStatus statusForKey(Object key) => _cacheForKey(key).statusForKey(key);
+2 -6
View File
@@ -27,9 +27,7 @@ class BrightnessFilter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: ColorFilter.matrix(
_ColorFilterGenerator.brightnessAdjustMatrix(brightness),
),
colorFilter: ColorFilter.matrix(_ColorFilterGenerator.brightnessAdjustMatrix(brightness)),
child: child,
);
}
@@ -44,9 +42,7 @@ class SaturationFilter extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: ColorFilter.matrix(
_ColorFilterGenerator.saturationAdjustMatrix(saturation),
),
colorFilter: ColorFilter.matrix(_ColorFilterGenerator.saturationAdjustMatrix(saturation)),
child: child,
);
}
+3 -17
View File
@@ -68,21 +68,10 @@ Debouncer useDebouncer({
Duration interval = const Duration(milliseconds: 300),
Duration? maxWaitTime,
List<Object?>? keys,
}) =>
use(
_DebouncerHook(
interval: interval,
maxWaitTime: maxWaitTime,
keys: keys,
),
);
}) => use(_DebouncerHook(interval: interval, maxWaitTime: maxWaitTime, keys: keys));
class _DebouncerHook extends Hook<Debouncer> {
const _DebouncerHook({
required this.interval,
this.maxWaitTime,
super.keys,
});
const _DebouncerHook({required this.interval, this.maxWaitTime, super.keys});
final Duration interval;
final Duration? maxWaitTime;
@@ -92,10 +81,7 @@ class _DebouncerHook extends Hook<Debouncer> {
}
class _DebouncerHookState extends HookState<Debouncer, _DebouncerHook> {
late final debouncer = Debouncer(
interval: hook.interval,
maxWaitTime: hook.maxWaitTime,
);
late final debouncer = Debouncer(interval: hook.interval, maxWaitTime: hook.maxWaitTime);
@override
Debouncer build(_) => debouncer;
@@ -5,20 +5,12 @@ import 'package:flutter_hooks/flutter_hooks.dart';
///
/// See also:
/// - [DraggableScrollableController]
DraggableScrollableController useDraggableScrollController({
List<Object?>? keys,
}) {
return use(
_DraggableScrollControllerHook(
keys: keys,
),
);
DraggableScrollableController useDraggableScrollController({List<Object?>? keys}) {
return use(_DraggableScrollControllerHook(keys: keys));
}
class _DraggableScrollControllerHook extends Hook<DraggableScrollableController> {
const _DraggableScrollControllerHook({
super.keys,
});
const _DraggableScrollControllerHook({super.keys});
@override
HookState<DraggableScrollableController, Hook<DraggableScrollableController>> createState() =>
@@ -3,16 +3,11 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
ValueNotifier<T> useAppSettingsState<T>(
AppSettingsEnum<T> key,
) {
ValueNotifier<T> useAppSettingsState<T>(AppSettingsEnum<T> key) {
final notifier = useState<T>(Store.get(key.storeKey, key.defaultValue));
// Listen to changes to the notifier and update app settings
useValueChanged(
notifier.value,
(_, __) => Store.put(key.storeKey, notifier.value),
);
useValueChanged(notifier.value, (_, __) => Store.put(key.storeKey, notifier.value));
return notifier;
}
+2 -6
View File
@@ -10,9 +10,7 @@ ObjectRef<Uint8List?> useBlurHashRef(Asset? asset) {
return useRef(null);
}
final rbga = thumbhash.thumbHashToRGBA(
base64Decode(asset!.thumbhash!),
);
final rbga = thumbhash.thumbHashToRGBA(base64Decode(asset!.thumbhash!));
return useRef(thumbhash.rgbaToBmp(rbga));
}
@@ -22,9 +20,7 @@ ObjectRef<Uint8List?> useDriftBlurHashRef(RemoteAsset? asset) {
return useRef(null);
}
final rbga = thumbhash.thumbHashToRGBA(
base64Decode(asset!.thumbHash!),
);
final rbga = thumbhash.thumbHashToRGBA(base64Decode(asset!.thumbHash!));
return useRef(thumbhash.rgbaToBmp(rbga));
}
@@ -4,9 +4,5 @@ import 'dart:ui'; // Import the dart:ui library for Rect
/// A hook that provides a [CropController] instance.
CropController useCropController() {
return useMemoized(
() => CropController(
defaultCrop: const Rect.fromLTRB(0, 0, 1, 1),
),
);
return useMemoized(() => CropController(defaultCrop: const Rect.fromLTRB(0, 0, 1, 1)));
}
+4 -7
View File
@@ -8,11 +8,8 @@ void useInterval(Duration delay, VoidCallback callback) {
final savedCallback = useRef(callback);
savedCallback.value = callback;
useEffect(
() {
final timer = Timer.periodic(delay, (_) => savedCallback.value());
return timer.cancel;
},
[delay],
);
useEffect(() {
final timer = Timer.periodic(delay, (_) => savedCallback.value());
return timer.cancel;
}, [delay]);
}
+3 -14
View File
@@ -2,26 +2,15 @@ import 'package:async/async.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
RestartableTimer useTimer(
Duration duration,
void Function() callback,
) {
return use(
_TimerHook(
duration: duration,
callback: callback,
),
);
RestartableTimer useTimer(Duration duration, void Function() callback) {
return use(_TimerHook(duration: duration, callback: callback));
}
class _TimerHook extends Hook<RestartableTimer> {
final Duration duration;
final void Function() callback;
const _TimerHook({
required this.duration,
required this.callback,
});
const _TimerHook({required this.duration, required this.callback});
@override
HookState<RestartableTimer, Hook<RestartableTimer>> createState() => _TimerHookState();
}
+1 -5
View File
@@ -10,11 +10,7 @@ class HttpSSLCertOverride extends HttpOverrides {
final SSLClientCertStoreVal? _clientCert;
late final SecurityContext? _ctxWithCert;
HttpSSLCertOverride(
this._allowSelfSignedSSLCert,
this._serverHost,
this._clientCert,
) {
HttpSSLCertOverride(this._allowSelfSignedSSLCert, this._serverHost, this._clientCert) {
if (_clientCert != null) {
_ctxWithCert = SecurityContext(withTrustedRoots: true);
if (_ctxWithCert != null) {
+6 -9
View File
@@ -31,15 +31,12 @@ class HttpSSLOptions {
HttpOverrides.global = HttpSSLCertOverride(allowSelfSignedSSLCert, serverHost, clientCert);
if (applyNative && Platform.isAndroid) {
_channel.invokeMethod("apply", [
allowSelfSignedSSLCert,
serverHost,
clientCert?.data,
clientCert?.password,
]).onError<PlatformException>((e, _) {
final log = Logger("HttpSSLOptions");
log.severe('Failed to set SSL options', e.message);
});
_channel
.invokeMethod("apply", [allowSelfSignedSSLCert, serverHost, clientCert?.data, clientCert?.password])
.onError<PlatformException>((e, _) {
final log = Logger("HttpSSLOptions");
log.severe('Failed to set SSL options', e.message);
});
}
}
}
+8 -32
View File
@@ -5,24 +5,15 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:openapi/api.dart';
String getThumbnailUrl(
final Asset asset, {
AssetMediaSize type = AssetMediaSize.thumbnail,
}) {
String getThumbnailUrl(final Asset asset, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
return getThumbnailUrlForRemoteId(asset.remoteId!, type: type);
}
String getThumbnailCacheKey(
final Asset asset, {
AssetMediaSize type = AssetMediaSize.thumbnail,
}) {
String getThumbnailCacheKey(final Asset asset, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
return getThumbnailCacheKeyForRemoteId(asset.remoteId!, type: type);
}
String getThumbnailCacheKeyForRemoteId(
final String id, {
AssetMediaSize type = AssetMediaSize.thumbnail,
}) {
String getThumbnailCacheKeyForRemoteId(final String id, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
if (type == AssetMediaSize.thumbnail) {
return 'thumbnail-image-$id';
} else {
@@ -30,30 +21,18 @@ String getThumbnailCacheKeyForRemoteId(
}
}
String getAlbumThumbnailUrl(
final Album album, {
AssetMediaSize type = AssetMediaSize.thumbnail,
}) {
String getAlbumThumbnailUrl(final Album album, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
if (album.thumbnail.value?.remoteId == null) {
return '';
}
return getThumbnailUrlForRemoteId(
album.thumbnail.value!.remoteId!,
type: type,
);
return getThumbnailUrlForRemoteId(album.thumbnail.value!.remoteId!, type: type);
}
String getAlbumThumbNailCacheKey(
final Album album, {
AssetMediaSize type = AssetMediaSize.thumbnail,
}) {
String getAlbumThumbNailCacheKey(final Album album, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
if (album.thumbnail.value?.remoteId == null) {
return '';
}
return getThumbnailCacheKeyForRemoteId(
album.thumbnail.value!.remoteId!,
type: type,
);
return getThumbnailCacheKeyForRemoteId(album.thumbnail.value!.remoteId!, type: type);
}
String getOriginalUrlForRemoteId(final String id) {
@@ -66,10 +45,7 @@ String getImageCacheKey(final Asset asset) {
return '${isFromDto ? asset.remoteId : asset.id}_fullStage';
}
String getThumbnailUrlForRemoteId(
final String id, {
AssetMediaSize type = AssetMediaSize.thumbnail,
}) {
String getThumbnailUrlForRemoteId(final String id, {AssetMediaSize type = AssetMediaSize.thumbnail}) {
return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}';
}
+1 -4
View File
@@ -9,10 +9,7 @@ final _loadingEntry = OverlayEntry(
child: DecoratedBox(
decoration: BoxDecoration(color: context.colorScheme.surface.withAlpha(200)),
child: const Center(
child: DelayedLoadingIndicator(
delay: Duration(seconds: 1),
fadeInDuration: Duration(milliseconds: 400),
),
child: DelayedLoadingIndicator(delay: Duration(seconds: 1), fadeInDuration: Duration(milliseconds: 400)),
),
),
),
+2 -8
View File
@@ -51,15 +51,9 @@ Cancelable<T?> runInIsolateGentle<T>({
HttpSSLOptions.apply(applyNative: false);
return await computation(ref);
} on CanceledError {
log.warning(
"Computation cancelled ${debugLabel == null ? '' : ' for $debugLabel'}",
);
log.warning("Computation cancelled ${debugLabel == null ? '' : ' for $debugLabel'}");
} catch (error, stack) {
log.severe(
"Error in runInIsolateGentle ${debugLabel == null ? '' : ' for $debugLabel'}",
error,
stack,
);
log.severe("Error in runInIsolateGentle ${debugLabel == null ? '' : ' for $debugLabel'}", error, stack);
} finally {
try {
await LogService.I.flushBuffer();
+28 -34
View File
@@ -48,21 +48,18 @@ class MapUtils {
);
static Map<String, dynamic> _addFeature(MapMarker marker) => {
'type': 'Feature',
'id': marker.assetRemoteId,
'geometry': {
'type': 'Point',
'coordinates': [marker.latLng.longitude, marker.latLng.latitude],
},
};
'type': 'Feature',
'id': marker.assetRemoteId,
'geometry': {
'type': 'Point',
'coordinates': [marker.latLng.longitude, marker.latLng.latitude],
},
};
static Map<String, dynamic> generateGeoJsonForMarkers(
List<MapMarker> markers,
) =>
{
'type': 'FeatureCollection',
'features': markers.map(_addFeature).toList(),
};
static Map<String, dynamic> generateGeoJsonForMarkers(List<MapMarker> markers) => {
'type': 'FeatureCollection',
'features': markers.map(_addFeature).toList(),
};
static Future<(Position?, LocationPermission?)> checkPermAndGetLocation({
required BuildContext context,
@@ -71,10 +68,7 @@ class MapUtils {
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled && !silent) {
showDialog(
context: context,
builder: (context) => _LocationServiceDisabledDialog(),
);
showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog());
return (null, LocationPermission.deniedForever);
}
@@ -116,24 +110,24 @@ class MapUtils {
class _LocationServiceDisabledDialog extends ConfirmDialog {
_LocationServiceDisabledDialog()
: super(
title: 'map_location_service_disabled_title'.tr(),
content: 'map_location_service_disabled_content'.tr(),
cancel: 'cancel'.tr(),
ok: 'yes'.tr(),
onOk: () async {
await Geolocator.openLocationSettings();
},
);
: super(
title: 'map_location_service_disabled_title'.tr(),
content: 'map_location_service_disabled_content'.tr(),
cancel: 'cancel'.tr(),
ok: 'yes'.tr(),
onOk: () async {
await Geolocator.openLocationSettings();
},
);
}
class _LocationPermissionDisabledDialog extends ConfirmDialog {
_LocationPermissionDisabledDialog()
: super(
title: 'map_no_location_permission_title'.tr(),
content: 'map_no_location_permission_content'.tr(),
cancel: 'cancel'.tr(),
ok: 'yes'.tr(),
onOk: () {},
);
: super(
title: 'map_no_location_permission_title'.tr(),
content: 'map_no_location_permission_content'.tr(),
cancel: 'cancel'.tr(),
ok: 'yes'.tr(),
onOk: () {},
);
}
+25 -58
View File
@@ -85,16 +85,14 @@ Future<void> _migrateTo(Isar db, int version) async {
Future<void> _migrateDeviceAsset(Isar db) async {
final ids = Platform.isAndroid
? (await db.androidDeviceAssets.where().findAll())
.map((a) => _DeviceAsset(assetId: a.id.toString(), hash: a.hash))
.toList()
.map((a) => _DeviceAsset(assetId: a.id.toString(), hash: a.hash))
.toList()
: (await db.iOSDeviceAssets.where().findAll()).map((i) => _DeviceAsset(assetId: i.id, hash: i.hash)).toList();
final PermissionState ps = await PhotoManager.requestPermissionExtend();
if (!ps.hasAccess) {
if (kDebugMode) {
debugPrint(
"[MIGRATION] Photo library permission not granted. Skipping device asset migration.",
);
debugPrint("[MIGRATION] Photo library permission not granted. Skipping device asset migration.");
}
return;
@@ -105,9 +103,7 @@ Future<void> _migrateDeviceAsset(Isar db) async {
if (paths.isEmpty) {
localAssets = (await db.assets.where().anyOf(ids, (query, id) => query.localIdEqualTo(id.assetId)).findAll())
.map(
(a) => _DeviceAsset(assetId: a.localId!, dateTime: a.fileModifiedAt),
)
.map((a) => _DeviceAsset(assetId: a.localId!, dateTime: a.fileModifiedAt))
.toList();
} else {
final AssetPathEntity albumWithAll = paths.first;
@@ -129,34 +125,24 @@ Future<void> _migrateDeviceAsset(Isar db) async {
compare: (a, b) => a.assetId.compareTo(b.assetId),
both: (deviceAsset, asset) {
toAdd.add(
DeviceAssetEntity(
assetId: deviceAsset.assetId,
hash: deviceAsset.hash!,
modifiedTime: asset.dateTime!,
),
DeviceAssetEntity(assetId: deviceAsset.assetId, hash: deviceAsset.hash!, modifiedTime: asset.dateTime!),
);
return false;
},
onlyFirst: (deviceAsset) {
if (kDebugMode) {
debugPrint(
'[MIGRATION] Local asset not found in DeviceAsset: ${deviceAsset.assetId}',
);
debugPrint('[MIGRATION] Local asset not found in DeviceAsset: ${deviceAsset.assetId}');
}
},
onlySecond: (asset) {
if (kDebugMode) {
debugPrint(
'[MIGRATION] Local asset not found in DeviceAsset: ${asset.assetId}',
);
debugPrint('[MIGRATION] Local asset not found in DeviceAsset: ${asset.assetId}');
}
},
);
if (kDebugMode) {
debugPrint(
"[MIGRATION] Total number of device assets migrated - ${toAdd.length}",
);
debugPrint("[MIGRATION] Total number of device assets migrated - ${toAdd.length}");
}
await db.writeTxn(() async {
@@ -171,24 +157,17 @@ Future<void> migrateDeviceAssetToSqlite(Isar db, Drift drift) async {
for (final deviceAsset in isarDeviceAssets) {
batch.update(
drift.localAssetEntity,
LocalAssetEntityCompanion(
checksum: Value(base64.encode(deviceAsset.hash)),
),
LocalAssetEntityCompanion(checksum: Value(base64.encode(deviceAsset.hash))),
where: (t) => t.id.equals(deviceAsset.assetId),
);
}
});
} catch (error) {
debugPrint(
"[MIGRATION] Error while migrating device assets to SQLite: $error",
);
debugPrint("[MIGRATION] Error while migrating device assets to SQLite: $error");
}
}
Future<void> migrateBackupAlbumsToSqlite(
Isar db,
Drift drift,
) async {
Future<void> migrateBackupAlbumsToSqlite(Isar db, Drift drift) async {
try {
final isarBackupAlbums = await db.backupAlbums.where().findAll();
// Recents is a virtual album on Android, and we don't have it with the new sync
@@ -197,23 +176,17 @@ Future<void> migrateBackupAlbumsToSqlite(
final recentAlbum = isarBackupAlbums.firstWhereOrNull((album) => album.id == 'isAll');
if (recentAlbum != null) {
await drift.localAlbumEntity.update().write(
const LocalAlbumEntityCompanion(
backupSelection: Value(BackupSelection.selected),
),
);
const LocalAlbumEntityCompanion(backupSelection: Value(BackupSelection.selected)),
);
final excluded = isarBackupAlbums
.where(
(album) => album.selection == isar_backup_album.BackupSelection.exclude,
)
.where((album) => album.selection == isar_backup_album.BackupSelection.exclude)
.map((album) => album.id)
.toList();
await drift.batch((batch) async {
for (final id in excluded) {
batch.update(
drift.localAlbumEntity,
const LocalAlbumEntityCompanion(
backupSelection: Value(BackupSelection.excluded),
),
const LocalAlbumEntityCompanion(backupSelection: Value(BackupSelection.excluded)),
where: (t) => t.id.equals(id),
);
}
@@ -227,22 +200,18 @@ Future<void> migrateBackupAlbumsToSqlite(
batch.update(
drift.localAlbumEntity,
LocalAlbumEntityCompanion(
backupSelection: Value(
switch (album.selection) {
isar_backup_album.BackupSelection.none => BackupSelection.none,
isar_backup_album.BackupSelection.select => BackupSelection.selected,
isar_backup_album.BackupSelection.exclude => BackupSelection.excluded,
},
),
backupSelection: Value(switch (album.selection) {
isar_backup_album.BackupSelection.none => BackupSelection.none,
isar_backup_album.BackupSelection.select => BackupSelection.selected,
isar_backup_album.BackupSelection.exclude => BackupSelection.excluded,
}),
),
where: (t) => t.id.equals(album.id),
);
}
});
} catch (error) {
debugPrint(
"[MIGRATION] Error while migrating backup albums to SQLite: $error",
);
debugPrint("[MIGRATION] Error while migrating backup albums to SQLite: $error");
}
}
@@ -259,12 +228,10 @@ Future<void> runNewSync(WidgetRef ref, {bool full = false}) async {
final backgroundManager = ref.read(backgroundSyncProvider);
Future.wait([
backgroundManager.syncLocal(full: full).then(
(_) {
Logger("runNewSync").fine("Hashing assets after syncLocal");
backgroundManager.hashAssets();
},
),
backgroundManager.syncLocal(full: full).then((_) {
Logger("runNewSync").fine("Hashing assets after syncLocal");
backgroundManager.hashAssets();
}),
backgroundManager.syncRemote(),
]);
}
+2 -10
View File
@@ -17,16 +17,8 @@ dynamic upgradeDto(dynamic value, String targetType) {
break;
case 'ServerConfigDto':
if (value is Map) {
addDefault(
value,
'mapLightStyleUrl',
'https://tiles.immich.cloud/v1/style/light.json',
);
addDefault(
value,
'mapDarkStyleUrl',
'https://tiles.immich.cloud/v1/style/dark.json',
);
addDefault(value, 'mapLightStyleUrl', 'https://tiles.immich.cloud/v1/style/light.json');
addDefault(value, 'mapDarkStyleUrl', 'https://tiles.immich.cloud/v1/style/dark.json');
}
case 'UserResponseDto':
if (value is Map) {
+9 -36
View File
@@ -1,55 +1,37 @@
import 'package:collection/collection.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
typedef AlbumSortFn = List<RemoteAlbum> Function(
List<RemoteAlbum> albums,
bool isReverse,
);
typedef AlbumSortFn = List<RemoteAlbum> Function(List<RemoteAlbum> albums, bool isReverse);
class _RemoteAlbumSortHandlers {
const _RemoteAlbumSortHandlers._();
static const AlbumSortFn created = _sortByCreated;
static List<RemoteAlbum> _sortByCreated(
List<RemoteAlbum> albums,
bool isReverse,
) {
static List<RemoteAlbum> _sortByCreated(List<RemoteAlbum> albums, bool isReverse) {
final sorted = albums.sortedBy((album) => album.createdAt);
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn title = _sortByTitle;
static List<RemoteAlbum> _sortByTitle(
List<RemoteAlbum> albums,
bool isReverse,
) {
static List<RemoteAlbum> _sortByTitle(List<RemoteAlbum> albums, bool isReverse) {
final sorted = albums.sortedBy((album) => album.name);
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn lastModified = _sortByLastModified;
static List<RemoteAlbum> _sortByLastModified(
List<RemoteAlbum> albums,
bool isReverse,
) {
static List<RemoteAlbum> _sortByLastModified(List<RemoteAlbum> albums, bool isReverse) {
final sorted = albums.sortedBy((album) => album.updatedAt);
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn assetCount = _sortByAssetCount;
static List<RemoteAlbum> _sortByAssetCount(
List<RemoteAlbum> albums,
bool isReverse,
) {
static List<RemoteAlbum> _sortByAssetCount(List<RemoteAlbum> albums, bool isReverse) {
final sorted = albums.sorted((a, b) => a.assetCount.compareTo(b.assetCount));
return (isReverse ? sorted.reversed : sorted).toList();
}
static const AlbumSortFn mostRecent = _sortByMostRecent;
static List<RemoteAlbum> _sortByMostRecent(
List<RemoteAlbum> albums,
bool isReverse,
) {
static List<RemoteAlbum> _sortByMostRecent(List<RemoteAlbum> albums, bool isReverse) {
final sorted = albums.sorted((a, b) {
// For most recent, we sort by updatedAt in descending order
return b.updatedAt.compareTo(a.updatedAt);
@@ -58,10 +40,7 @@ class _RemoteAlbumSortHandlers {
}
static const AlbumSortFn mostOldest = _sortByMostOldest;
static List<RemoteAlbum> _sortByMostOldest(
List<RemoteAlbum> albums,
bool isReverse,
) {
static List<RemoteAlbum> _sortByMostOldest(List<RemoteAlbum> albums, bool isReverse) {
final sorted = albums.sorted((a, b) {
// For oldest, we sort by createdAt in ascending order
return a.createdAt.compareTo(b.createdAt);
@@ -72,14 +51,8 @@ class _RemoteAlbumSortHandlers {
enum RemoteAlbumSortMode {
title("library_page_sort_title", _RemoteAlbumSortHandlers.title),
assetCount(
"library_page_sort_asset_count",
_RemoteAlbumSortHandlers.assetCount,
),
lastModified(
"library_page_sort_last_modified",
_RemoteAlbumSortHandlers.lastModified,
),
assetCount("library_page_sort_asset_count", _RemoteAlbumSortHandlers.assetCount),
lastModified("library_page_sort_last_modified", _RemoteAlbumSortHandlers.lastModified),
created("library_page_sort_created", _RemoteAlbumSortHandlers.created),
mostRecent("sort_recent", _RemoteAlbumSortHandlers.mostRecent),
mostOldest("sort_oldest", _RemoteAlbumSortHandlers.mostOldest);
+19 -51
View File
@@ -16,27 +16,21 @@ import 'package:immich_mobile/widgets/common/location_picker.dart';
import 'package:immich_mobile/widgets/common/share_dialog.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
void handleShareAssets(
WidgetRef ref,
BuildContext context,
Iterable<Asset> selection,
) {
void handleShareAssets(WidgetRef ref, BuildContext context, Iterable<Asset> selection) {
showDialog(
context: context,
builder: (BuildContext buildContext) {
ref.watch(shareServiceProvider).shareAssets(selection.toList(), context).then(
(bool status) {
if (!status) {
ImmichToast.show(
context: context,
msg: 'image_viewer_page_state_provider_share_error'.tr(),
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
}
buildContext.pop();
},
);
ref.watch(shareServiceProvider).shareAssets(selection.toList(), context).then((bool status) {
if (!status) {
ImmichToast.show(
context: context,
msg: 'image_viewer_page_state_provider_share_error'.tr(),
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
}
buildContext.pop();
});
return const ShareDialog();
},
barrierDismissible: false,
@@ -58,11 +52,7 @@ Future<void> handleArchiveAssets(
? 'moved_to_archive'.t(context: context, args: {'count': selection.length})
: 'moved_to_library'.t(context: context, args: {'count': selection.length});
if (context.mounted) {
ImmichToast.show(
context: context,
msg: message,
gravity: toastGravity,
);
ImmichToast.show(context: context, msg: message, gravity: toastGravity);
}
}
}
@@ -83,20 +73,12 @@ Future<void> handleFavoriteAssets(
? 'Added ${selection.length} $assetOrAssets to favorites'
: 'Removed ${selection.length} $assetOrAssets from favorites';
if (context.mounted) {
ImmichToast.show(
context: context,
msg: toastMessage,
gravity: toastGravity,
);
ImmichToast.show(context: context, msg: toastMessage, gravity: toastGravity);
}
}
}
Future<void> handleEditDateTime(
WidgetRef ref,
BuildContext context,
List<Asset> selection,
) async {
Future<void> handleEditDateTime(WidgetRef ref, BuildContext context, List<Asset> selection) async {
DateTime? initialDate;
String? timeZone;
Duration? offset;
@@ -122,27 +104,17 @@ Future<void> handleEditDateTime(
ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime);
}
Future<void> handleEditLocation(
WidgetRef ref,
BuildContext context,
List<Asset> selection,
) async {
Future<void> handleEditLocation(WidgetRef ref, BuildContext context, List<Asset> selection) async {
LatLng? initialLatLng;
if (selection.length == 1) {
final asset = selection.first;
final assetWithExif = await ref.watch(assetServiceProvider).loadExif(asset);
if (assetWithExif.exifInfo?.latitude != null && assetWithExif.exifInfo?.longitude != null) {
initialLatLng = LatLng(
assetWithExif.exifInfo!.latitude!,
assetWithExif.exifInfo!.longitude!,
);
initialLatLng = LatLng(assetWithExif.exifInfo!.latitude!, assetWithExif.exifInfo!.longitude!);
}
}
final location = await showLocationPicker(
context: context,
initialLatLng: initialLatLng,
);
final location = await showLocationPicker(context: context, initialLatLng: initialLatLng);
if (location == null) {
return;
@@ -165,11 +137,7 @@ Future<void> handleSetAssetsVisibility(
? 'Added ${selection.length} $assetOrAssets to locked folder'
: 'Removed ${selection.length} $assetOrAssets from locked folder';
if (context.mounted) {
ImmichToast.show(
context: context,
msg: toastMessage,
gravity: ToastGravity.BOTTOM,
);
ImmichToast.show(context: context, msg: toastMessage, gravity: ToastGravity.BOTTOM);
}
}
}
+2 -8
View File
@@ -25,17 +25,11 @@ class Throttler {
/// Creates a [Throttler] that will be disposed automatically. If no [interval] is provided, a
/// default interval of 300ms is used to throttle the function calls
Throttler useThrottler({
Duration interval = const Duration(milliseconds: 300),
List<Object?>? keys,
}) =>
Throttler useThrottler({Duration interval = const Duration(milliseconds: 300), List<Object?>? keys}) =>
use(_ThrottleHook(interval: interval, keys: keys));
class _ThrottleHook extends Hook<Throttler> {
const _ThrottleHook({
required this.interval,
super.keys,
});
const _ThrottleHook({required this.interval, super.keys});
final Duration interval;
+3 -8
View File
@@ -3,12 +3,7 @@ import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
String getAltText(
ExifInfo? exifInfo,
DateTime fileCreatedAt,
AssetType type,
List<String> peopleNames,
) {
String getAltText(ExifInfo? exifInfo, DateTime fileCreatedAt, AssetType type, List<String> peopleNames) {
if (exifInfo?.description != null && exifInfo!.description!.isNotEmpty) {
return exifInfo.description!;
}
@@ -41,14 +36,14 @@ String getAltText(
1 => "image_alt_text_date_place_1_person",
2 => "image_alt_text_date_place_2_people",
3 => "image_alt_text_date_place_3_people",
_ => "image_alt_text_date_place_4_or_more_people"
_ => "image_alt_text_date_place_4_or_more_people",
})
: (switch (peopleNames.length) {
0 => "image_alt_text_date",
1 => "image_alt_text_date_1_person",
2 => "image_alt_text_date_2_people",
3 => "image_alt_text_date_3_people",
_ => "image_alt_text_date_4_or_more_people"
_ => "image_alt_text_date_4_or_more_people",
});
return (template, args);
}
+18 -16
View File
@@ -44,13 +44,14 @@ String punycodeEncodeUrl(String serverUrl) {
final serverUri = Uri.tryParse(serverUrl);
if (serverUri == null || serverUri.host.isEmpty) return '';
final encodedHost = Uri.decodeComponent(serverUri.host).split('.').map(
(segment) {
// If segment is already ASCII, then return as it is.
if (segment.runes.every((c) => c < 0x80)) return segment;
return 'xn--${punycodeEncode(segment)}';
},
).join('.');
final encodedHost = Uri.decodeComponent(serverUri.host)
.split('.')
.map((segment) {
// If segment is already ASCII, then return as it is.
if (segment.runes.every((c) => c < 0x80)) return segment;
return 'xn--${punycodeEncode(segment)}';
})
.join('.');
return serverUri.replace(host: encodedHost).toString();
}
@@ -76,15 +77,16 @@ String? punycodeDecodeUrl(String? serverUrl) {
final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null;
if (serverUri == null || serverUri.host.isEmpty) return null;
final decodedHost = serverUri.host.split('.').map(
(segment) {
if (segment.toLowerCase().startsWith('xn--')) {
return punycodeDecode(segment.substring(4));
}
// If segment is not punycode encoded, then return as it is.
return segment;
},
).join('.');
final decodedHost = serverUri.host
.split('.')
.map((segment) {
if (segment.toLowerCase().startsWith('xn--')) {
return punycodeDecode(segment.substring(4));
}
// If segment is not punycode encoded, then return as it is.
return segment;
})
.join('.');
return Uri.decodeFull(serverUri.replace(host: decodedHost).toString());
}
+1 -6
View File
@@ -1,9 +1,4 @@
String? getVersionCompatibilityMessage(
int appMajor,
int appMinor,
int serverMajor,
int serverMinor,
) {
String? getVersionCompatibilityMessage(int appMajor, int appMinor, int serverMajor, int serverMinor) {
if (serverMajor != appMajor) {
return 'Your app major version is not compatible with the server!';
}