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:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}';
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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: () {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
|
||||
@@ -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,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!';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user