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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
643 changed files with 6519 additions and 18431 deletions

View file

@ -1,17 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
enum ImmichColorPreset { enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray }
indigo,
deepPurple,
pink,
red,
orange,
yellow,
lime,
green,
cyan,
slateGray,
}
const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo; const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo;
const String defaultColorPresetName = "indigo"; const String defaultColorPresetName = "indigo";

View file

@ -1,13 +1,6 @@
enum SortOrder { enum SortOrder { asc, desc }
asc,
desc,
}
enum TextSearchType { enum TextSearchType { context, filename, description }
context,
filename,
description,
}
enum AssetVisibilityEnum { timeline, hidden, archive, locked } enum AssetVisibilityEnum { timeline, hidden, archive, locked }

View file

@ -2,511 +2,49 @@ import 'package:flutter/material.dart';
const List<ColorFilter> filters = [ const List<ColorFilter> filters = [
//Original //Original
ColorFilter.matrix([ ColorFilter.matrix([1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0]),
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
]),
//Vintage //Vintage
ColorFilter.matrix([ ColorFilter.matrix([0.8, 0.1, 0.1, 0, 20, 0.1, 0.8, 0.1, 0, 20, 0.1, 0.1, 0.8, 0, 20, 0, 0, 0, 1, 0]),
0.8,
0.1,
0.1,
0,
20,
0.1,
0.8,
0.1,
0,
20,
0.1,
0.1,
0.8,
0,
20,
0,
0,
0,
1,
0,
]),
//Mood //Mood
ColorFilter.matrix([ ColorFilter.matrix([1.2, 0.1, 0.1, 0, 10, 0.1, 1, 0.1, 0, 10, 0.1, 0.1, 1, 0, 10, 0, 0, 0, 1, 0]),
1.2,
0.1,
0.1,
0,
10,
0.1,
1,
0.1,
0,
10,
0.1,
0.1,
1,
0,
10,
0,
0,
0,
1,
0,
]),
//Crisp //Crisp
ColorFilter.matrix([ ColorFilter.matrix([1.2, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1, 0]),
1.2,
0,
0,
0,
0,
0,
1.2,
0,
0,
0,
0,
0,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
//Cool //Cool
ColorFilter.matrix([ ColorFilter.matrix([0.9, 0, 0.2, 0, 0, 0, 1, 0.1, 0, 0, 0.1, 0, 1.2, 0, 0, 0, 0, 0, 1, 0]),
0.9,
0,
0.2,
0,
0,
0,
1,
0.1,
0,
0,
0.1,
0,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
//Blush //Blush
ColorFilter.matrix([ ColorFilter.matrix([1.1, 0.1, 0.1, 0, 10, 0.1, 1, 0.1, 0, 10, 0.1, 0.1, 1, 0, 5, 0, 0, 0, 1, 0]),
1.1,
0.1,
0.1,
0,
10,
0.1,
1,
0.1,
0,
10,
0.1,
0.1,
1,
0,
5,
0,
0,
0,
1,
0,
]),
//Sunkissed //Sunkissed
ColorFilter.matrix([ ColorFilter.matrix([1.3, 0, 0.1, 0, 15, 0, 1.1, 0.1, 0, 10, 0, 0, 0.9, 0, 5, 0, 0, 0, 1, 0]),
1.3,
0,
0.1,
0,
15,
0,
1.1,
0.1,
0,
10,
0,
0,
0.9,
0,
5,
0,
0,
0,
1,
0,
]),
//Fresh //Fresh
ColorFilter.matrix([ ColorFilter.matrix([1.2, 0, 0, 0, 20, 0, 1.2, 0, 0, 20, 0, 0, 1.1, 0, 20, 0, 0, 0, 1, 0]),
1.2,
0,
0,
0,
20,
0,
1.2,
0,
0,
20,
0,
0,
1.1,
0,
20,
0,
0,
0,
1,
0,
]),
//Classic //Classic
ColorFilter.matrix([ ColorFilter.matrix([1.1, 0, -0.1, 0, 10, -0.1, 1.1, 0.1, 0, 5, 0, -0.1, 1.1, 0, 0, 0, 0, 0, 1, 0]),
1.1,
0,
-0.1,
0,
10,
-0.1,
1.1,
0.1,
0,
5,
0,
-0.1,
1.1,
0,
0,
0,
0,
0,
1,
0,
]),
//Lomo-ish //Lomo-ish
ColorFilter.matrix([ ColorFilter.matrix([1.5, 0, 0.1, 0, 0, 0, 1.45, 0, 0, 0, 0.1, 0, 1.3, 0, 0, 0, 0, 0, 1, 0]),
1.5,
0,
0.1,
0,
0,
0,
1.45,
0,
0,
0,
0.1,
0,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
//Nashville //Nashville
ColorFilter.matrix([ ColorFilter.matrix([1.2, 0.15, -0.15, 0, 15, 0.1, 1.1, 0.1, 0, 10, -0.05, 0.2, 1.25, 0, 5, 0, 0, 0, 1, 0]),
1.2,
0.15,
-0.15,
0,
15,
0.1,
1.1,
0.1,
0,
10,
-0.05,
0.2,
1.25,
0,
5,
0,
0,
0,
1,
0,
]),
//Valencia //Valencia
ColorFilter.matrix([ ColorFilter.matrix([1.15, 0.1, 0.1, 0, 20, 0.1, 1.1, 0, 0, 10, 0.1, 0.1, 1.2, 0, 5, 0, 0, 0, 1, 0]),
1.15,
0.1,
0.1,
0,
20,
0.1,
1.1,
0,
0,
10,
0.1,
0.1,
1.2,
0,
5,
0,
0,
0,
1,
0,
]),
//Clarendon //Clarendon
ColorFilter.matrix([ ColorFilter.matrix([1.2, 0, 0, 0, 10, 0, 1.25, 0, 0, 10, 0, 0, 1.3, 0, 10, 0, 0, 0, 1, 0]),
1.2,
0,
0,
0,
10,
0,
1.25,
0,
0,
10,
0,
0,
1.3,
0,
10,
0,
0,
0,
1,
0,
]),
//Moon //Moon
ColorFilter.matrix([ ColorFilter.matrix([0.33, 0.33, 0.33, 0, 0, 0.33, 0.33, 0.33, 0, 0, 0.33, 0.33, 0.33, 0, 0, 0, 0, 0, 1, 0]),
0.33,
0.33,
0.33,
0,
0,
0.33,
0.33,
0.33,
0,
0,
0.33,
0.33,
0.33,
0,
0,
0,
0,
0,
1,
0,
]),
//Willow //Willow
ColorFilter.matrix([ ColorFilter.matrix([0.5, 0.5, 0.5, 0, 20, 0.5, 0.5, 0.5, 0, 20, 0.5, 0.5, 0.5, 0, 20, 0, 0, 0, 1, 0]),
0.5,
0.5,
0.5,
0,
20,
0.5,
0.5,
0.5,
0,
20,
0.5,
0.5,
0.5,
0,
20,
0,
0,
0,
1,
0,
]),
//Kodak //Kodak
ColorFilter.matrix([ ColorFilter.matrix([1.3, 0.1, -0.1, 0, 10, 0, 1.25, 0.1, 0, 10, 0, -0.1, 1.1, 0, 5, 0, 0, 0, 1, 0]),
1.3,
0.1,
-0.1,
0,
10,
0,
1.25,
0.1,
0,
10,
0,
-0.1,
1.1,
0,
5,
0,
0,
0,
1,
0,
]),
//Frost //Frost
ColorFilter.matrix([ ColorFilter.matrix([0.8, 0.2, 0.1, 0, 0, 0.2, 1.1, 0.1, 0, 0, 0.1, 0.1, 1.2, 0, 10, 0, 0, 0, 1, 0]),
0.8,
0.2,
0.1,
0,
0,
0.2,
1.1,
0.1,
0,
0,
0.1,
0.1,
1.2,
0,
10,
0,
0,
0,
1,
0,
]),
//Night Vision //Night Vision
ColorFilter.matrix([ ColorFilter.matrix([0.1, 0.95, 0.2, 0, 0, 0.1, 1.5, 0.1, 0, 0, 0.2, 0.7, 0, 0, 0, 0, 0, 0, 1, 0]),
0.1,
0.95,
0.2,
0,
0,
0.1,
1.5,
0.1,
0,
0,
0.2,
0.7,
0,
0,
0,
0,
0,
0,
1,
0,
]),
//Sunset //Sunset
ColorFilter.matrix([ ColorFilter.matrix([1.5, 0.2, 0, 0, 0, 0.1, 0.9, 0.1, 0, 0, -0.1, -0.2, 1.3, 0, 0, 0, 0, 0, 1, 0]),
1.5,
0.2,
0,
0,
0,
0.1,
0.9,
0.1,
0,
0,
-0.1,
-0.2,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
//Noir //Noir
ColorFilter.matrix([ ColorFilter.matrix([1.3, -0.3, 0.1, 0, 0, -0.1, 1.2, -0.1, 0, 0, 0.1, -0.2, 1.3, 0, 0, 0, 0, 0, 1, 0]),
1.3,
-0.3,
0.1,
0,
0,
-0.1,
1.2,
-0.1,
0,
0,
0.1,
-0.2,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
//Dreamy //Dreamy
ColorFilter.matrix([ ColorFilter.matrix([1.1, 0.1, 0.1, 0, 0, 0.1, 1.1, 0.1, 0, 0, 0.1, 0.1, 1.1, 0, 15, 0, 0, 0, 1, 0]),
1.1,
0.1,
0.1,
0,
0,
0.1,
1.1,
0.1,
0,
0,
0.1,
0.1,
1.1,
0,
15,
0,
0,
0,
1,
0,
]),
//Sepia //Sepia
ColorFilter.matrix([ ColorFilter.matrix([0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]),
0.393,
0.769,
0.189,
0,
0,
0.349,
0.686,
0.168,
0,
0,
0.272,
0.534,
0.131,
0,
0,
0,
0,
0,
1,
0,
]),
//Radium //Radium
ColorFilter.matrix([ ColorFilter.matrix([
1.438, 1.438,
@ -554,212 +92,23 @@ const List<ColorFilter> filters = [
0, 0,
]), ]),
//Purple Haze //Purple Haze
ColorFilter.matrix([ ColorFilter.matrix([1.3, 0, 1.2, 0, 0, 0, 1.1, 0, 0, 0, 0.2, 0, 1.3, 0, 0, 0, 0, 0, 1, 0]),
1.3,
0,
1.2,
0,
0,
0,
1.1,
0,
0,
0,
0.2,
0,
1.3,
0,
0,
0,
0,
0,
1,
0,
]),
//Lemonade //Lemonade
ColorFilter.matrix([ ColorFilter.matrix([1.2, 0.1, 0, 0, 0, 0, 1.1, 0.2, 0, 0, 0.1, 0, 0.7, 0, 0, 0, 0, 0, 1, 0]),
1.2,
0.1,
0,
0,
0,
0,
1.1,
0.2,
0,
0,
0.1,
0,
0.7,
0,
0,
0,
0,
0,
1,
0,
]),
//Caramel //Caramel
ColorFilter.matrix([ ColorFilter.matrix([1.6, 0.2, 0, 0, 0, 0.1, 1.3, 0.1, 0, 0, 0, 0.1, 0.9, 0, 0, 0, 0, 0, 1, 0]),
1.6,
0.2,
0,
0,
0,
0.1,
1.3,
0.1,
0,
0,
0,
0.1,
0.9,
0,
0,
0,
0,
0,
1,
0,
]),
//Peachy //Peachy
ColorFilter.matrix([ ColorFilter.matrix([1.3, 0.5, 0, 0, 0, 0.2, 1.1, 0.3, 0, 0, 0.1, 0.1, 1.2, 0, 0, 0, 0, 0, 1, 0]),
1.3,
0.5,
0,
0,
0,
0.2,
1.1,
0.3,
0,
0,
0.1,
0.1,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
//Neon //Neon
ColorFilter.matrix([ ColorFilter.matrix([1, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 1, 0]),
1,
0,
1,
0,
0,
0,
2,
0,
0,
0,
0,
0,
3,
0,
0,
0,
0,
0,
1,
0,
]),
//Cold Morning //Cold Morning
ColorFilter.matrix([ ColorFilter.matrix([0.9, 0.1, 0.2, 0, 0, 0, 1, 0.1, 0, 0, 0.1, 0, 1.2, 0, 0, 0, 0, 0, 1, 0]),
0.9,
0.1,
0.2,
0,
0,
0,
1,
0.1,
0,
0,
0.1,
0,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
//Lush //Lush
ColorFilter.matrix([ ColorFilter.matrix([0.9, 0.2, 0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 1.1, 0, 0, 0, 0, 0, 1, 0]),
0.9,
0.2,
0,
0,
0,
0,
1.2,
0,
0,
0,
0,
0,
1.1,
0,
0,
0,
0,
0,
1,
0,
]),
//Urban Neon //Urban Neon
ColorFilter.matrix([ ColorFilter.matrix([1.1, 0, 0.3, 0, 0, 0, 0.9, 0.3, 0, 0, 0.3, 0.1, 1.2, 0, 0, 0, 0, 0, 1, 0]),
1.1,
0,
0.3,
0,
0,
0,
0.9,
0.3,
0,
0,
0.3,
0.1,
1.2,
0,
0,
0,
0,
0,
1,
0,
]),
//Monochrome //Monochrome
ColorFilter.matrix([ ColorFilter.matrix([0.6, 0.2, 0.2, 0, 0, 0.2, 0.6, 0.2, 0, 0, 0.2, 0.2, 0.7, 0, 0, 0, 0, 0, 1, 0]),
0.6,
0.2,
0.2,
0,
0,
0.2,
0.6,
0.2,
0,
0,
0.2,
0.2,
0.7,
0,
0,
0,
0,
0,
1,
0,
]),
]; ];
const List<String> filterNames = [ const List<String> filterNames = [

View file

@ -51,7 +51,4 @@ const Map<String, Locale> locales = {
const String translationsPath = 'assets/i18n'; const String translationsPath = 'assets/i18n';
const List<Locale> localesNotSupportedByOverpass = [ const List<Locale> localesNotSupportedByOverpass = [Locale('el', 'GR'), Locale('sr', 'Cyrl')];
Locale('el', 'GR'),
Locale('sr', 'Cyrl'),
];

View file

@ -9,11 +9,7 @@ enum AssetType {
audio, audio,
} }
enum AssetState { enum AssetState { local, remote, merged }
local,
remote,
merged,
}
sealed class BaseAsset { sealed class BaseAsset {
final String name; final String name;

View file

@ -1,11 +1,6 @@
part of 'base_asset.model.dart'; part of 'base_asset.model.dart';
enum AssetVisibility { enum AssetVisibility { timeline, hidden, archive, locked }
timeline,
hidden,
archive,
locked,
}
// Model for an asset stored in the server // Model for an asset stored in the server
class RemoteAsset extends BaseAsset { class RemoteAsset extends BaseAsset {

View file

@ -5,11 +5,7 @@ class DeviceAsset {
final Uint8List hash; final Uint8List hash;
final DateTime modifiedTime; final DateTime modifiedTime;
const DeviceAsset({ const DeviceAsset({required this.assetId, required this.hash, required this.modifiedTime});
required this.assetId,
required this.hash,
required this.modifiedTime,
});
@override @override
bool operator ==(covariant DeviceAsset other) { bool operator ==(covariant DeviceAsset other) {
@ -28,11 +24,7 @@ class DeviceAsset {
return 'DeviceAsset(assetId: $assetId, hash: $hash, modifiedTime: $modifiedTime)'; return 'DeviceAsset(assetId: $assetId, hash: $hash, modifiedTime: $modifiedTime)';
} }
DeviceAsset copyWith({ DeviceAsset copyWith({String? assetId, Uint8List? hash, DateTime? modifiedTime}) {
String? assetId,
Uint8List? hash,
DateTime? modifiedTime,
}) {
return DeviceAsset( return DeviceAsset(
assetId: assetId ?? this.assetId, assetId: assetId ?? this.assetId,
hash: hash ?? this.hash, hash: hash ?? this.hash,

View file

@ -1,16 +1,5 @@
/// Log levels according to dart logging [Level] /// Log levels according to dart logging [Level]
enum LogLevel { enum LogLevel { all, finest, finer, fine, config, info, warning, severe, shout, off }
all,
finest,
finer,
fine,
config,
info,
warning,
severe,
shout,
off,
}
class LogMessage { class LogMessage {
final String message; final String message;

View file

@ -13,28 +13,18 @@ enum MemoryTypeEnum {
class MemoryData { class MemoryData {
final int year; final int year;
const MemoryData({ const MemoryData({required this.year});
required this.year,
});
MemoryData copyWith({ MemoryData copyWith({int? year}) {
int? year, return MemoryData(year: year ?? this.year);
}) {
return MemoryData(
year: year ?? this.year,
);
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return <String, dynamic>{ return <String, dynamic>{'year': year};
'year': year,
};
} }
factory MemoryData.fromMap(Map<String, dynamic> map) { factory MemoryData.fromMap(Map<String, dynamic> map) {
return MemoryData( return MemoryData(year: map['year'] as int);
year: map['year'] as int,
);
} }
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());

View file

@ -5,21 +5,12 @@ class SearchResult {
final List<BaseAsset> assets; final List<BaseAsset> assets;
final int? nextPage; final int? nextPage;
const SearchResult({ const SearchResult({required this.assets, this.nextPage});
required this.assets,
this.nextPage,
});
int get totalAssets => assets.length; int get totalAssets => assets.length;
SearchResult copyWith({ SearchResult copyWith({List<BaseAsset>? assets, int? nextPage}) {
List<BaseAsset>? assets, return SearchResult(assets: assets ?? this.assets, nextPage: nextPage ?? this.nextPage);
int? nextPage,
}) {
return SearchResult(
assets: assets ?? this.assets,
nextPage: nextPage ?? this.nextPage,
);
} }
@override @override

View file

@ -8,8 +8,7 @@ enum Setting<T> {
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false), loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
preferRemoteImage<bool>(StoreKey.preferRemoteImage, false), preferRemoteImage<bool>(StoreKey.preferRemoteImage, false),
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false), advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
enableBackup<bool>(StoreKey.enableBackup, false), enableBackup<bool>(StoreKey.enableBackup, false);
;
const Setting(this.storeKey, this.defaultValue); const Setting(this.storeKey, this.defaultValue);

View file

@ -14,13 +14,7 @@ class Stack {
required this.primaryAssetId, required this.primaryAssetId,
}); });
Stack copyWith({ Stack copyWith({String? id, DateTime? createdAt, DateTime? updatedAt, String? ownerId, String? primaryAssetId}) {
String? id,
DateTime? createdAt,
DateTime? updatedAt,
String? ownerId,
String? primaryAssetId,
}) {
return Stack( return Stack(
id: id ?? this.id, id: id ?? this.id,
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
@ -63,11 +57,7 @@ class StackResponse {
final String primaryAssetId; final String primaryAssetId;
final List<String> assetIds; final List<String> assetIds;
const StackResponse({ const StackResponse({required this.id, required this.primaryAssetId, required this.assetIds});
required this.id,
required this.primaryAssetId,
required this.assetIds,
});
@override @override
bool operator ==(covariant StackResponse other) { bool operator ==(covariant StackResponse other) {

View file

@ -1,18 +1,8 @@
import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart';
enum GroupAssetsBy { enum GroupAssetsBy { day, month, auto, none }
day,
month,
auto,
none;
}
enum HeaderType { enum HeaderType { none, month, day, monthAndDay }
none,
month,
day,
monthAndDay;
}
class Bucket { class Bucket {
final int assetCount; final int assetCount;

View file

@ -74,22 +74,21 @@ quotaSizeInBytes: $quotaSizeInBytes,
bool? isPartnerSharedWith, bool? isPartnerSharedWith,
int? quotaUsageInBytes, int? quotaUsageInBytes,
int? quotaSizeInBytes, int? quotaSizeInBytes,
}) => }) => UserDto(
UserDto( id: id ?? this.id,
id: id ?? this.id, email: email ?? this.email,
email: email ?? this.email, name: name ?? this.name,
name: name ?? this.name, isAdmin: isAdmin ?? this.isAdmin,
isAdmin: isAdmin ?? this.isAdmin, updatedAt: updatedAt ?? this.updatedAt,
updatedAt: updatedAt ?? this.updatedAt, profileImagePath: profileImagePath ?? this.profileImagePath,
profileImagePath: profileImagePath ?? this.profileImagePath, avatarColor: avatarColor ?? this.avatarColor,
avatarColor: avatarColor ?? this.avatarColor, memoryEnabled: memoryEnabled ?? this.memoryEnabled,
memoryEnabled: memoryEnabled ?? this.memoryEnabled, inTimeline: inTimeline ?? this.inTimeline,
inTimeline: inTimeline ?? this.inTimeline, isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy,
isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy, isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith,
isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith, quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes,
quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes,
quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, );
);
@override @override
bool operator ==(covariant UserDto other) { bool operator ==(covariant UserDto other) {
@ -143,13 +142,7 @@ class PartnerUserDto {
this.profileImagePath, this.profileImagePath,
}); });
PartnerUserDto copyWith({ PartnerUserDto copyWith({String? id, String? email, String? name, bool? inTimeline, String? profileImagePath}) {
String? id,
String? email,
String? name,
bool? inTimeline,
String? profileImagePath,
}) {
return PartnerUserDto( return PartnerUserDto(
id: id ?? this.id, id: id ?? this.id,
email: email ?? this.email, email: email ?? this.email,

View file

@ -24,17 +24,17 @@ enum AvatarColor {
const AvatarColor(this.value); const AvatarColor(this.value);
Color toColor({bool isDarkTheme = false}) => switch (this) { Color toColor({bool isDarkTheme = false}) => switch (this) {
AvatarColor.primary => isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF), AvatarColor.primary => isDarkTheme ? const Color(0xFFABCBFA) : const Color(0xFF4250AF),
AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182), AvatarColor.pink => const Color.fromARGB(255, 244, 114, 182),
AvatarColor.red => const Color.fromARGB(255, 239, 68, 68), AvatarColor.red => const Color.fromARGB(255, 239, 68, 68),
AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8), AvatarColor.yellow => const Color.fromARGB(255, 234, 179, 8),
AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246), AvatarColor.blue => const Color.fromARGB(255, 59, 130, 246),
AvatarColor.green => const Color.fromARGB(255, 22, 163, 74), AvatarColor.green => const Color.fromARGB(255, 22, 163, 74),
AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234), AvatarColor.purple => const Color.fromARGB(255, 147, 51, 234),
AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12), AvatarColor.orange => const Color.fromARGB(255, 234, 88, 12),
AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99), AvatarColor.gray => const Color.fromARGB(255, 75, 85, 99),
AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6), AvatarColor.amber => const Color.fromARGB(255, 217, 119, 6),
}; };
} }
class Onboarding { class Onboarding {
@ -193,17 +193,9 @@ class License {
final String activationKey; final String activationKey;
final String licenseKey; final String licenseKey;
const License({ const License({required this.activatedAt, required this.activationKey, required this.licenseKey});
required this.activatedAt,
required this.activationKey,
required this.licenseKey,
});
License copyWith({ License copyWith({DateTime? activatedAt, String? activationKey, String? licenseKey}) {
DateTime? activatedAt,
String? activationKey,
String? licenseKey,
}) {
return License( return License(
activatedAt: activatedAt ?? this.activatedAt, activatedAt: activatedAt ?? this.activatedAt,
activationKey: activationKey ?? this.activationKey, activationKey: activationKey ?? this.activationKey,
@ -255,16 +247,11 @@ class UserMetadata {
final Preferences? preferences; final Preferences? preferences;
final License? license; final License? license;
const UserMetadata({ const UserMetadata({required this.userId, required this.key, this.onboarding, this.preferences, this.license})
required this.userId, : assert(
required this.key, onboarding != null || preferences != null || license != null,
this.onboarding, 'One of onboarding, preferences and license must be provided',
this.preferences, );
this.license,
}) : assert(
onboarding != null || preferences != null || license != null,
'One of onboarding, preferences and license must be provided',
);
UserMetadata copyWith({ UserMetadata copyWith({
String? userId, String? userId,

View file

@ -13,9 +13,9 @@ class AssetService {
const AssetService({ const AssetService({
required RemoteAssetRepository remoteAssetRepository, required RemoteAssetRepository remoteAssetRepository,
required DriftLocalAssetRepository localAssetRepository, required DriftLocalAssetRepository localAssetRepository,
}) : _remoteAssetRepository = remoteAssetRepository, }) : _remoteAssetRepository = remoteAssetRepository,
_localAssetRepository = localAssetRepository, _localAssetRepository = localAssetRepository,
_platform = const LocalPlatform(); _platform = const LocalPlatform();
Stream<BaseAsset?> watchAsset(BaseAsset asset) { Stream<BaseAsset?> watchAsset(BaseAsset asset) {
final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id; final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id;

View file

@ -25,19 +25,16 @@ class HashService {
required NativeSyncApi nativeSyncApi, required NativeSyncApi nativeSyncApi,
this.batchSizeLimit = kBatchHashSizeLimit, this.batchSizeLimit = kBatchHashSizeLimit,
this.batchFileLimit = kBatchHashFileLimit, this.batchFileLimit = kBatchHashFileLimit,
}) : _localAlbumRepository = localAlbumRepository, }) : _localAlbumRepository = localAlbumRepository,
_localAssetRepository = localAssetRepository, _localAssetRepository = localAssetRepository,
_storageRepository = storageRepository, _storageRepository = storageRepository,
_nativeSyncApi = nativeSyncApi; _nativeSyncApi = nativeSyncApi;
Future<void> hashAssets() async { Future<void> hashAssets() async {
final Stopwatch stopwatch = Stopwatch()..start(); final Stopwatch stopwatch = Stopwatch()..start();
// Sorted by backupSelection followed by isCloud // Sorted by backupSelection followed by isCloud
final localAlbums = await _localAlbumRepository.getAll( final localAlbums = await _localAlbumRepository.getAll(
sortBy: { sortBy: {SortLocalAlbumsBy.backupSelection, SortLocalAlbumsBy.isIosSharedAlbum},
SortLocalAlbumsBy.backupSelection,
SortLocalAlbumsBy.isIosSharedAlbum,
},
); );
for (final album in localAlbums) { for (final album in localAlbums) {

View file

@ -21,9 +21,9 @@ class LocalSyncService {
required DriftLocalAlbumRepository localAlbumRepository, required DriftLocalAlbumRepository localAlbumRepository,
required NativeSyncApi nativeSyncApi, required NativeSyncApi nativeSyncApi,
Platform? platform, Platform? platform,
}) : _localAlbumRepository = localAlbumRepository, }) : _localAlbumRepository = localAlbumRepository,
_nativeSyncApi = nativeSyncApi, _nativeSyncApi = nativeSyncApi,
_platform = platform ?? const LocalPlatform(); _platform = platform ?? const LocalPlatform();
Future<void> sync({bool full = false}) async { Future<void> sync({bool full = false}) async {
final Stopwatch stopwatch = Stopwatch()..start(); final Stopwatch stopwatch = Stopwatch()..start();
@ -70,9 +70,7 @@ class LocalSyncService {
for (final album in cloudAlbums) { for (final album in cloudAlbums) {
final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id); final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id);
if (dbAlbum == null) { if (dbAlbum == null) {
_log.warning( _log.warning("Cloud album ${album.name} not found in local database. Skipping sync.");
"Cloud album ${album.name} not found in local database. Skipping sync.",
);
continue; continue;
} }
await updateAlbum(dbAlbum, album); await updateAlbum(dbAlbum, album);
@ -120,10 +118,7 @@ class LocalSyncService {
final assets = album.assetCount > 0 ? await _nativeSyncApi.getAssetsForAlbum(album.id) : <PlatformAsset>[]; final assets = album.assetCount > 0 ? await _nativeSyncApi.getAssetsForAlbum(album.id) : <PlatformAsset>[];
await _localAlbumRepository.upsert( await _localAlbumRepository.upsert(album, toUpsert: assets.toLocalAssets());
album,
toUpsert: assets.toLocalAssets(),
);
_log.fine("Successfully added device album ${album.name}"); _log.fine("Successfully added device album ${album.name}");
} catch (e, s) { } catch (e, s) {
_log.warning("Error while adding device album", e, s); _log.warning("Error while adding device album", e, s);
@ -146,9 +141,7 @@ class LocalSyncService {
_log.fine("Syncing device album ${dbAlbum.name}"); _log.fine("Syncing device album ${dbAlbum.name}");
if (_albumsEqual(deviceAlbum, dbAlbum)) { if (_albumsEqual(deviceAlbum, dbAlbum)) {
_log.fine( _log.fine("Device album ${dbAlbum.name} has not changed. Skipping sync.");
"Device album ${dbAlbum.name} has not changed. Skipping sync.",
);
return false; return false;
} }
@ -172,10 +165,7 @@ class LocalSyncService {
@visibleForTesting @visibleForTesting
// The [deviceAlbum] is expected to be refreshed before calling this method // The [deviceAlbum] is expected to be refreshed before calling this method
// with modified time and asset count // with modified time and asset count
Future<bool> checkAddition( Future<bool> checkAddition(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async {
LocalAlbum dbAlbum,
LocalAlbum deviceAlbum,
) async {
try { try {
_log.fine("Fast syncing device album ${dbAlbum.name}"); _log.fine("Fast syncing device album ${dbAlbum.name}");
// Assets has been modified // Assets has been modified
@ -189,9 +179,7 @@ class LocalSyncService {
// Early return if no new assets were found // Early return if no new assets were found
if (newAssetsCount == 0) { if (newAssetsCount == 0) {
_log.fine( _log.fine("No new assets found despite album having changes. Proceeding to full sync for ${dbAlbum.name}");
"No new assets found despite album having changes. Proceeding to full sync for ${dbAlbum.name}",
);
return false; return false;
} }
@ -201,10 +189,7 @@ class LocalSyncService {
return false; return false;
} }
final newAssets = await _nativeSyncApi.getAssetsForAlbum( final newAssets = await _nativeSyncApi.getAssetsForAlbum(deviceAlbum.id, updatedTimeCond: updatedTime);
deviceAlbum.id,
updatedTimeCond: updatedTime,
);
await _localAlbumRepository.upsert( await _localAlbumRepository.upsert(
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection), deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
@ -229,9 +214,7 @@ class LocalSyncService {
final assetsInDb = dbAlbum.assetCount > 0 ? await _localAlbumRepository.getAssets(dbAlbum.id) : <LocalAsset>[]; final assetsInDb = dbAlbum.assetCount > 0 ? await _localAlbumRepository.getAssets(dbAlbum.id) : <LocalAsset>[];
if (deviceAlbum.assetCount == 0) { if (deviceAlbum.assetCount == 0) {
_log.fine( _log.fine("Device album ${deviceAlbum.name} is empty. Removing assets from DB.");
"Device album ${deviceAlbum.name} is empty. Removing assets from DB.",
);
await _localAlbumRepository.upsert( await _localAlbumRepository.upsert(
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection), deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
toDelete: assetsInDb.map((a) => a.id), toDelete: assetsInDb.map((a) => a.id),
@ -239,18 +222,11 @@ class LocalSyncService {
return true; return true;
} }
final updatedDeviceAlbum = deviceAlbum.copyWith( final updatedDeviceAlbum = deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection);
backupSelection: dbAlbum.backupSelection,
);
if (dbAlbum.assetCount == 0) { if (dbAlbum.assetCount == 0) {
_log.fine( _log.fine("Device album ${deviceAlbum.name} is empty. Adding assets to DB.");
"Device album ${deviceAlbum.name} is empty. Adding assets to DB.", await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsInDevice);
);
await _localAlbumRepository.upsert(
updatedDeviceAlbum,
toUpsert: assetsInDevice,
);
return true; return true;
} }
@ -282,18 +258,12 @@ class LocalSyncService {
); );
if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) { if (assetsToUpsert.isEmpty && assetsToDelete.isEmpty) {
_log.fine( _log.fine("No asset changes detected in album ${deviceAlbum.name}. Updating metadata.");
"No asset changes detected in album ${deviceAlbum.name}. Updating metadata.",
);
_localAlbumRepository.upsert(updatedDeviceAlbum); _localAlbumRepository.upsert(updatedDeviceAlbum);
return true; return true;
} }
await _localAlbumRepository.upsert( await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsToUpsert, toDelete: assetsToDelete);
updatedDeviceAlbum,
toUpsert: assetsToUpsert,
toDelete: assetsToDelete,
);
return true; return true;
} catch (e, s) { } catch (e, s) {

View file

@ -61,11 +61,7 @@ class LogService {
return instance; return instance;
} }
LogService._( LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) {
this._logRepository,
this._storeRepository,
this._shouldBuffer,
) {
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord); _logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
} }
@ -89,10 +85,7 @@ class LogService {
if (_shouldBuffer) { if (_shouldBuffer) {
_msgBuffer.add(record); _msgBuffer.add(record);
_flushTimer ??= Timer( _flushTimer ??= Timer(const Duration(seconds: 5), () => unawaited(flushBuffer()));
const Duration(seconds: 5),
() => unawaited(flushBuffer()),
);
} else { } else {
unawaited(_logRepository.insert(record)); unawaited(_logRepository.insert(record));
} }

View file

@ -7,10 +7,7 @@ class DriftPartnerService {
final DriftPartnerRepository _driftPartnerRepository; final DriftPartnerRepository _driftPartnerRepository;
final PartnerApiRepository _partnerApiRepository; final PartnerApiRepository _partnerApiRepository;
const DriftPartnerService( const DriftPartnerService(this._driftPartnerRepository, this._partnerApiRepository);
this._driftPartnerRepository,
this._partnerApiRepository,
);
Future<List<PartnerUserDto>> getSharedWith(String userId) { Future<List<PartnerUserDto>> getSharedWith(String userId) {
return _driftPartnerRepository.getSharedWith(userId); return _driftPartnerRepository.getSharedWith(userId);
@ -20,9 +17,7 @@ class DriftPartnerService {
return _driftPartnerRepository.getSharedBy(userId); return _driftPartnerRepository.getSharedBy(userId);
} }
Future<List<PartnerUserDto>> getAvailablePartners( Future<List<PartnerUserDto>> getAvailablePartners(String currentUserId) async {
String currentUserId,
) async {
final otherUsers = await _driftPartnerRepository.getAvailablePartners(currentUserId); final otherUsers = await _driftPartnerRepository.getAvailablePartners(currentUserId);
final currentPartners = await _driftPartnerRepository.getSharedBy(currentUserId); final currentPartners = await _driftPartnerRepository.getSharedBy(currentUserId);
final available = otherUsers.where((user) { final available = otherUsers.where((user) {
@ -39,10 +34,7 @@ class DriftPartnerService {
return; return;
} }
await _partnerApiRepository.update( await _partnerApiRepository.update(partnerId, inTimeline: !partner.inTimeline);
partnerId,
inTimeline: !partner.inTimeline,
);
await _driftPartnerRepository.toggleShowInTimeline(partner, userId); await _driftPartnerRepository.toggleShowInTimeline(partner, userId);
} }

View file

@ -26,11 +26,7 @@ class RemoteAlbumService {
return _repository.get(albumId); return _repository.get(albumId);
} }
List<RemoteAlbum> sortAlbums( List<RemoteAlbum> sortAlbums(List<RemoteAlbum> albums, RemoteAlbumSortMode sortMode, {bool isReverse = false}) {
List<RemoteAlbum> albums,
RemoteAlbumSortMode sortMode, {
bool isReverse = false,
}) {
return sortMode.sortFn(albums, isReverse); return sortMode.sortFn(albums, isReverse);
} }
@ -69,16 +65,8 @@ class RemoteAlbumService {
return filtered; return filtered;
} }
Future<RemoteAlbum> createAlbum({ Future<RemoteAlbum> createAlbum({required String title, required List<String> assetIds, String? description}) async {
required String title, final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds);
required List<String> assetIds,
String? description,
}) async {
final album = await _albumApiRepository.createDriftAlbum(
title,
description: description,
assetIds: assetIds,
);
await _repository.create(album, assetIds); await _repository.create(album, assetIds);
@ -120,14 +108,8 @@ class RemoteAlbumService {
return _repository.getAssets(albumId); return _repository.getAssets(albumId);
} }
Future<int> addAssets({ Future<int> addAssets({required String albumId, required List<String> assetIds}) async {
required String albumId, final album = await _albumApiRepository.addAssets(albumId, assetIds);
required List<String> assetIds,
}) async {
final album = await _albumApiRepository.addAssets(
albumId,
assetIds,
);
await _repository.addAssets(albumId, album.added); await _repository.addAssets(albumId, album.added);
@ -140,10 +122,7 @@ class RemoteAlbumService {
await _repository.deleteAlbum(albumId); await _repository.deleteAlbum(albumId);
} }
Future<void> addUsers({ Future<void> addUsers({required String albumId, required List<String> userIds}) async {
required String albumId,
required List<String> userIds,
}) async {
await _albumApiRepository.addUsers(albumId, userIds); await _albumApiRepository.addUsers(albumId, userIds);
return _repository.addUsers(albumId, userIds); return _repository.addUsers(albumId, userIds);

View file

@ -83,10 +83,10 @@ extension on AssetResponseDto {
extension on AssetTypeEnum { extension on AssetTypeEnum {
AssetType toAssetType() => switch (this) { AssetType toAssetType() => switch (this) {
AssetTypeEnum.IMAGE => AssetType.image, AssetTypeEnum.IMAGE => AssetType.image,
AssetTypeEnum.VIDEO => AssetType.video, AssetTypeEnum.VIDEO => AssetType.video,
AssetTypeEnum.AUDIO => AssetType.audio, AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.OTHER => AssetType.other, AssetTypeEnum.OTHER => AssetType.other,
_ => throw Exception('Unknown AssetType value: $this'), _ => throw Exception('Unknown AssetType value: $this'),
}; };
} }

View file

@ -24,16 +24,12 @@ class StoreService {
} }
// TODO: Replace the implementation with the one from create after removing the typedef // TODO: Replace the implementation with the one from create after removing the typedef
static Future<StoreService> init({ static Future<StoreService> init({required IsarStoreRepository storeRepository}) async {
required IsarStoreRepository storeRepository,
}) async {
_instance ??= await create(storeRepository: storeRepository); _instance ??= await create(storeRepository: storeRepository);
return _instance!; return _instance!;
} }
static Future<StoreService> create({ static Future<StoreService> create({required IsarStoreRepository storeRepository}) async {
required IsarStoreRepository storeRepository,
}) async {
final instance = StoreService._(storeRepository: storeRepository); final instance = StoreService._(storeRepository: storeRepository);
await instance._populateCache(); await instance._populateCache();
instance._storeUpdateSubscription = instance._listenForChange(); instance._storeUpdateSubscription = instance._listenForChange();
@ -48,8 +44,8 @@ class StoreService {
} }
StreamSubscription<StoreDto> _listenForChange() => _storeRepository.watchAll().listen((event) { StreamSubscription<StoreDto> _listenForChange() => _storeRepository.watchAll().listen((event) {
_cache[event.key.id] = event.value; _cache[event.key.id] = event.value;
}); });
/// Disposes the store and cancels the subscription. To reuse the store call init() again /// Disposes the store and cancels the subscription. To reuse the store call init() again
void dispose() async { void dispose() async {

View file

@ -18,9 +18,9 @@ class SyncStreamService {
required SyncApiRepository syncApiRepository, required SyncApiRepository syncApiRepository,
required SyncStreamRepository syncStreamRepository, required SyncStreamRepository syncStreamRepository,
bool Function()? cancelChecker, bool Function()? cancelChecker,
}) : _syncApiRepository = syncApiRepository, }) : _syncApiRepository = syncApiRepository,
_syncStreamRepository = syncStreamRepository, _syncStreamRepository = syncStreamRepository,
_cancelChecker = cancelChecker; _cancelChecker = cancelChecker;
bool get isCancelled => _cancelChecker?.call() ?? false; bool get isCancelled => _cancelChecker?.call() ?? false;
@ -34,9 +34,7 @@ class SyncStreamService {
Future<void> handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) async { Future<void> handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) async {
if (batchData.isEmpty) return; if (batchData.isEmpty) return;
_logger.info( _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV1 events');
'Processing batch of ${batchData.length} AssetUploadReadyV1 events',
);
final List<SyncAssetV1> assets = []; final List<SyncAssetV1> assets = [];
final List<SyncAssetExifV1> exifs = []; final List<SyncAssetExifV1> exifs = [];
@ -65,22 +63,12 @@ class SyncStreamService {
} }
if (assets.isNotEmpty && exifs.isNotEmpty) { if (assets.isNotEmpty && exifs.isNotEmpty) {
await _syncStreamRepository.updateAssetsV1( await _syncStreamRepository.updateAssetsV1(assets, debugLabel: 'websocket-batch');
assets, await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
debugLabel: 'websocket-batch',
);
await _syncStreamRepository.updateAssetsExifV1(
exifs,
debugLabel: 'websocket-batch',
);
_logger.info('Successfully processed ${assets.length} assets in batch'); _logger.info('Successfully processed ${assets.length} assets in batch');
} }
} catch (error, stackTrace) { } catch (error, stackTrace) {
_logger.severe( _logger.severe("Error processing AssetUploadReadyV1 websocket batch events", error, stackTrace);
"Error processing AssetUploadReadyV1 websocket batch events",
error,
stackTrace,
);
} }
} }
@ -114,10 +102,7 @@ class SyncStreamService {
batch.clear(); batch.clear();
} }
Future<void> _handleSyncData( Future<void> _handleSyncData(SyncEntityType type, Iterable<Object> data) async {
SyncEntityType type,
Iterable<Object> data,
) async {
_logger.fine("Processing sync data for $type of length ${data.length}"); _logger.fine("Processing sync data for $type of length ${data.length}");
switch (type) { switch (type) {
case SyncEntityType.userV1: case SyncEntityType.userV1:
@ -135,30 +120,15 @@ class SyncStreamService {
case SyncEntityType.assetExifV1: case SyncEntityType.assetExifV1:
return _syncStreamRepository.updateAssetsExifV1(data.cast()); return _syncStreamRepository.updateAssetsExifV1(data.cast());
case SyncEntityType.partnerAssetV1: case SyncEntityType.partnerAssetV1:
return _syncStreamRepository.updateAssetsV1( return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
data.cast(),
debugLabel: 'partner',
);
case SyncEntityType.partnerAssetBackfillV1: case SyncEntityType.partnerAssetBackfillV1:
return _syncStreamRepository.updateAssetsV1( return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
data.cast(),
debugLabel: 'partner backfill',
);
case SyncEntityType.partnerAssetDeleteV1: case SyncEntityType.partnerAssetDeleteV1:
return _syncStreamRepository.deleteAssetsV1( return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
data.cast(),
debugLabel: "partner",
);
case SyncEntityType.partnerAssetExifV1: case SyncEntityType.partnerAssetExifV1:
return _syncStreamRepository.updateAssetsExifV1( return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner');
data.cast(),
debugLabel: 'partner',
);
case SyncEntityType.partnerAssetExifBackfillV1: case SyncEntityType.partnerAssetExifBackfillV1:
return _syncStreamRepository.updateAssetsExifV1( return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill');
data.cast(),
debugLabel: 'partner backfill',
);
case SyncEntityType.albumV1: case SyncEntityType.albumV1:
return _syncStreamRepository.updateAlbumsV1(data.cast()); return _syncStreamRepository.updateAlbumsV1(data.cast());
case SyncEntityType.albumDeleteV1: case SyncEntityType.albumDeleteV1:
@ -166,39 +136,21 @@ class SyncStreamService {
case SyncEntityType.albumUserV1: case SyncEntityType.albumUserV1:
return _syncStreamRepository.updateAlbumUsersV1(data.cast()); return _syncStreamRepository.updateAlbumUsersV1(data.cast());
case SyncEntityType.albumUserBackfillV1: case SyncEntityType.albumUserBackfillV1:
return _syncStreamRepository.updateAlbumUsersV1( return _syncStreamRepository.updateAlbumUsersV1(data.cast(), debugLabel: 'backfill');
data.cast(),
debugLabel: 'backfill',
);
case SyncEntityType.albumUserDeleteV1: case SyncEntityType.albumUserDeleteV1:
return _syncStreamRepository.deleteAlbumUsersV1(data.cast()); return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
case SyncEntityType.albumAssetV1: case SyncEntityType.albumAssetV1:
return _syncStreamRepository.updateAssetsV1( return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album');
data.cast(),
debugLabel: 'album',
);
case SyncEntityType.albumAssetBackfillV1: case SyncEntityType.albumAssetBackfillV1:
return _syncStreamRepository.updateAssetsV1( return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album backfill');
data.cast(),
debugLabel: 'album backfill',
);
case SyncEntityType.albumAssetExifV1: case SyncEntityType.albumAssetExifV1:
return _syncStreamRepository.updateAssetsExifV1( return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album');
data.cast(),
debugLabel: 'album',
);
case SyncEntityType.albumAssetExifBackfillV1: case SyncEntityType.albumAssetExifBackfillV1:
return _syncStreamRepository.updateAssetsExifV1( return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album backfill');
data.cast(),
debugLabel: 'album backfill',
);
case SyncEntityType.albumToAssetV1: case SyncEntityType.albumToAssetV1:
return _syncStreamRepository.updateAlbumToAssetsV1(data.cast()); return _syncStreamRepository.updateAlbumToAssetsV1(data.cast());
case SyncEntityType.albumToAssetBackfillV1: case SyncEntityType.albumToAssetBackfillV1:
return _syncStreamRepository.updateAlbumToAssetsV1( return _syncStreamRepository.updateAlbumToAssetsV1(data.cast(), debugLabel: 'backfill');
data.cast(),
debugLabel: 'backfill',
);
case SyncEntityType.albumToAssetDeleteV1: case SyncEntityType.albumToAssetDeleteV1:
return _syncStreamRepository.deleteAlbumToAssetsV1(data.cast()); return _syncStreamRepository.deleteAlbumToAssetsV1(data.cast());
// No-op. SyncAckV1 entities are checkpoints in the sync stream // No-op. SyncAckV1 entities are checkpoints in the sync stream
@ -218,28 +170,15 @@ class SyncStreamService {
case SyncEntityType.stackDeleteV1: case SyncEntityType.stackDeleteV1:
return _syncStreamRepository.deleteStacksV1(data.cast()); return _syncStreamRepository.deleteStacksV1(data.cast());
case SyncEntityType.partnerStackV1: case SyncEntityType.partnerStackV1:
return _syncStreamRepository.updateStacksV1( return _syncStreamRepository.updateStacksV1(data.cast(), debugLabel: 'partner');
data.cast(),
debugLabel: 'partner',
);
case SyncEntityType.partnerStackBackfillV1: case SyncEntityType.partnerStackBackfillV1:
return _syncStreamRepository.updateStacksV1( return _syncStreamRepository.updateStacksV1(data.cast(), debugLabel: 'partner backfill');
data.cast(),
debugLabel: 'partner backfill',
);
case SyncEntityType.partnerStackDeleteV1: case SyncEntityType.partnerStackDeleteV1:
return _syncStreamRepository.deleteStacksV1( return _syncStreamRepository.deleteStacksV1(data.cast(), debugLabel: 'partner');
data.cast(),
debugLabel: 'partner',
);
case SyncEntityType.userMetadataV1: case SyncEntityType.userMetadataV1:
return _syncStreamRepository.updateUserMetadatasV1( return _syncStreamRepository.updateUserMetadatasV1(data.cast());
data.cast(),
);
case SyncEntityType.userMetadataDeleteV1: case SyncEntityType.userMetadataDeleteV1:
return _syncStreamRepository.deleteUserMetadatasV1( return _syncStreamRepository.deleteUserMetadatasV1(data.cast());
data.cast(),
);
case SyncEntityType.personV1: case SyncEntityType.personV1:
return _syncStreamRepository.updatePeopleV1(data.cast()); return _syncStreamRepository.updatePeopleV1(data.cast());
case SyncEntityType.personDeleteV1: case SyncEntityType.personDeleteV1:

View file

@ -11,27 +11,19 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/async_mutex.dart';
typedef TimelineAssetSource = Future<List<BaseAsset>> Function( typedef TimelineAssetSource = Future<List<BaseAsset>> Function(int index, int count);
int index,
int count,
);
typedef TimelineBucketSource = Stream<List<Bucket>> Function(); typedef TimelineBucketSource = Stream<List<Bucket>> Function();
typedef TimelineQuery = ({ typedef TimelineQuery = ({TimelineAssetSource assetSource, TimelineBucketSource bucketSource});
TimelineAssetSource assetSource,
TimelineBucketSource bucketSource,
});
class TimelineFactory { class TimelineFactory {
final DriftTimelineRepository _timelineRepository; final DriftTimelineRepository _timelineRepository;
final SettingsService _settingsService; final SettingsService _settingsService;
const TimelineFactory({ const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService})
required DriftTimelineRepository timelineRepository, : _timelineRepository = timelineRepository,
required SettingsService settingsService, _settingsService = settingsService;
}) : _timelineRepository = timelineRepository,
_settingsService = settingsService;
GroupAssetsBy get groupBy { GroupAssetsBy get groupBy {
final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)]; final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)];
@ -75,17 +67,11 @@ class TimelineService {
int _totalAssets = 0; int _totalAssets = 0;
int get totalAssets => _totalAssets; int get totalAssets => _totalAssets;
TimelineService(TimelineQuery query) TimelineService(TimelineQuery query) : this._(assetSource: query.assetSource, bucketSource: query.bucketSource);
: this._(
assetSource: query.assetSource,
bucketSource: query.bucketSource,
);
TimelineService._({ TimelineService._({required TimelineAssetSource assetSource, required TimelineBucketSource bucketSource})
required TimelineAssetSource assetSource, : _assetSource = assetSource,
required TimelineBucketSource bucketSource, _bucketSource = bucketSource {
}) : _assetSource = assetSource,
_bucketSource = bucketSource {
_bucketSubscription = _bucketSource().listen((buckets) { _bucketSubscription = _bucketSource().listen((buckets) {
_mutex.run(() async { _mutex.run(() async {
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount); final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);
@ -103,10 +89,7 @@ class TimelineService {
count = kTimelineAssetLoadBatchSize; count = kTimelineAssetLoadBatchSize;
} else { } else {
offset = _bufferOffset; offset = _bufferOffset;
count = math.min( count = math.min(_buffer.length, totalAssets - _bufferOffset);
_buffer.length,
totalAssets - _bufferOffset,
);
} }
_buffer = await _assetSource(offset, count); _buffer = await _assetSource(offset, count);
_bufferOffset = offset; _bufferOffset = offset;
@ -134,10 +117,7 @@ class TimelineService {
// make sure to load a meaningful amount of data (and not only the requested slice) // make sure to load a meaningful amount of data (and not only the requested slice)
// otherwise, each call to [loadAssets] would result in DB call trashing performance // otherwise, each call to [loadAssets] would result in DB call trashing performance
// fills small requests to [kTimelineAssetLoadBatchSize], adds some legroom into the opposite scroll direction for large requests // fills small requests to [kTimelineAssetLoadBatchSize], adds some legroom into the opposite scroll direction for large requests
final len = math.max( final len = math.max(kTimelineAssetLoadBatchSize, count + kTimelineAssetLoadOppositeSize);
kTimelineAssetLoadBatchSize,
count + kTimelineAssetLoadOppositeSize,
);
// when scrolling forward, start shortly before the requested offset // when scrolling forward, start shortly before the requested offset
// when scrolling backward, end shortly after the requested offset to guard against the user scrolling // when scrolling backward, end shortly after the requested offset to guard against the user scrolling
// in the other direction a tiny bit resulting in another required load from the DB // in the other direction a tiny bit resulting in another required load from the DB

View file

@ -18,9 +18,9 @@ class UserService {
required IsarUserRepository isarUserRepository, required IsarUserRepository isarUserRepository,
required UserApiRepository userApiRepository, required UserApiRepository userApiRepository,
required StoreService storeService, required StoreService storeService,
}) : _isarUserRepository = isarUserRepository, }) : _isarUserRepository = isarUserRepository,
_userApiRepository = userApiRepository, _userApiRepository = userApiRepository,
_storeService = storeService; _storeService = storeService;
UserDto getMyUser() { UserDto getMyUser() {
return _storeService.get(StoreKey.currentUser); return _storeService.get(StoreKey.currentUser);
@ -44,10 +44,7 @@ class UserService {
Future<String?> createProfileImage(String name, Uint8List image) async { Future<String?> createProfileImage(String name, Uint8List image) async {
try { try {
final path = await _userApiRepository.createProfileImage( final path = await _userApiRepository.createProfileImage(name: name, data: image);
name: name,
data: image,
);
final updatedUser = getMyUser().copyWith(profileImagePath: path); final updatedUser = getMyUser().copyWith(profileImagePath: path);
await _storeService.put(StoreKey.currentUser, updatedUser); await _storeService.put(StoreKey.currentUser, updatedUser);
await _isarUserRepository.update(updatedUser); await _isarUserRepository.update(updatedUser);

View file

@ -66,23 +66,21 @@ class BackgroundSyncManager {
// We use a ternary operator to avoid [_deviceAlbumSyncTask] from being // We use a ternary operator to avoid [_deviceAlbumSyncTask] from being
// captured by the closure passed to [runInIsolateGentle]. // captured by the closure passed to [runInIsolateGentle].
_deviceAlbumSyncTask = full _deviceAlbumSyncTask = full
? runInIsolateGentle( ? runInIsolateGentle(computation: (ref) => ref.read(localSyncServiceProvider).sync(full: true))
computation: (ref) => ref.read(localSyncServiceProvider).sync(full: true), : runInIsolateGentle(computation: (ref) => ref.read(localSyncServiceProvider).sync(full: false));
)
: runInIsolateGentle(
computation: (ref) => ref.read(localSyncServiceProvider).sync(full: false),
);
return _deviceAlbumSyncTask!.whenComplete(() { return _deviceAlbumSyncTask!
_deviceAlbumSyncTask = null; .whenComplete(() {
onLocalSyncComplete?.call(); _deviceAlbumSyncTask = null;
}).catchError((error) { onLocalSyncComplete?.call();
onLocalSyncError?.call(error.toString()); })
_deviceAlbumSyncTask = null; .catchError((error) {
}); onLocalSyncError?.call(error.toString());
_deviceAlbumSyncTask = null;
});
} }
// No need to cancel the task, as it can also be run when the user logs out // No need to cancel the task, as it can also be run when the user logs out
Future<void> hashAssets() { Future<void> hashAssets() {
if (_hashTask != null) { if (_hashTask != null) {
return _hashTask!.future; return _hashTask!.future;
@ -90,17 +88,17 @@ class BackgroundSyncManager {
onHashingStart?.call(); onHashingStart?.call();
_hashTask = runInIsolateGentle( _hashTask = runInIsolateGentle(computation: (ref) => ref.read(hashServiceProvider).hashAssets());
computation: (ref) => ref.read(hashServiceProvider).hashAssets(),
);
return _hashTask!.whenComplete(() { return _hashTask!
onHashingComplete?.call(); .whenComplete(() {
_hashTask = null; onHashingComplete?.call();
}).catchError((error) { _hashTask = null;
onHashingError?.call(error.toString()); })
_hashTask = null; .catchError((error) {
}); onHashingError?.call(error.toString());
_hashTask = null;
});
} }
Future<void> syncRemote() { Future<void> syncRemote() {
@ -110,16 +108,16 @@ class BackgroundSyncManager {
onRemoteSyncStart?.call(); onRemoteSyncStart?.call();
_syncTask = runInIsolateGentle( _syncTask = runInIsolateGentle(computation: (ref) => ref.read(syncStreamServiceProvider).sync());
computation: (ref) => ref.read(syncStreamServiceProvider).sync(), return _syncTask!
); .whenComplete(() {
return _syncTask!.whenComplete(() { onRemoteSyncComplete?.call();
onRemoteSyncComplete?.call(); _syncTask = null;
_syncTask = null; })
}).catchError((error) { .catchError((error) {
onRemoteSyncError?.call(error.toString()); onRemoteSyncError?.call(error.toString());
_syncTask = null; _syncTask = null;
}); });
} }
Future<void> syncWebsocketBatch(List<dynamic> batchData) { Future<void> syncWebsocketBatch(List<dynamic> batchData) {
@ -133,9 +131,6 @@ class BackgroundSyncManager {
} }
} }
Cancelable<void> _handleWsAssetUploadReadyV1Batch( Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => runInIsolateGentle(
List<dynamic> batchData, computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV1Batch(batchData),
) => );
runInIsolateGentle(
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV1Batch(batchData),
);

View file

@ -28,12 +28,7 @@ class EventStream {
void Function()? onDone, void Function()? onDone,
bool? cancelOnError, bool? cancelOnError,
}) { }) {
return where<T>().listen( return where<T>().listen(onData, onError: onError, onDone: onDone, cancelOnError: cancelOnError);
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
} }
/// Closes the stream controller /// Closes the stream controller

View file

@ -113,10 +113,7 @@ class Album {
modifiedAt.isAtSameMomentAs(other.modifiedAt) && modifiedAt.isAtSameMomentAs(other.modifiedAt) &&
isAtSameMomentAs(startDate, other.startDate) && isAtSameMomentAs(startDate, other.startDate) &&
isAtSameMomentAs(endDate, other.endDate) && isAtSameMomentAs(endDate, other.endDate) &&
isAtSameMomentAs( isAtSameMomentAs(lastModifiedAssetTimestamp, other.lastModifiedAssetTimestamp) &&
lastModifiedAssetTimestamp,
other.lastModifiedAssetTimestamp,
) &&
shared == other.shared && shared == other.shared &&
activityEnabled == other.activityEnabled && activityEnabled == other.activityEnabled &&
owner.value == other.owner.value && owner.value == other.owner.value &&
@ -169,9 +166,7 @@ class Album {
a.thumbnail.value = await db.assets.where().remoteIdEqualTo(dto.albumThumbnailAssetId).findFirst(); a.thumbnail.value = await db.assets.where().remoteIdEqualTo(dto.albumThumbnailAssetId).findFirst();
} }
if (dto.albumUsers.isNotEmpty) { if (dto.albumUsers.isNotEmpty) {
final users = await db.users.getAllById( final users = await db.users.getAllById(dto.albumUsers.map((e) => e.user.id).toList(growable: false));
dto.albumUsers.map((e) => e.user.id).toList(growable: false),
);
a.sharedUsers.addAll(users.cast()); a.sharedUsers.addAll(users.cast());
} }
if (dto.assets.isNotEmpty) { if (dto.assets.isNotEmpty) {

Binary file not shown.

View file

@ -19,30 +19,30 @@ part 'asset.entity.g.dart';
@Collection(inheritance: false) @Collection(inheritance: false)
class Asset { class Asset {
Asset.remote(AssetResponseDto remote) Asset.remote(AssetResponseDto remote)
: remoteId = remote.id, : remoteId = remote.id,
checksum = remote.checksum, checksum = remote.checksum,
fileCreatedAt = remote.fileCreatedAt, fileCreatedAt = remote.fileCreatedAt,
fileModifiedAt = remote.fileModifiedAt, fileModifiedAt = remote.fileModifiedAt,
updatedAt = remote.updatedAt, updatedAt = remote.updatedAt,
durationInSeconds = remote.duration.toDuration()?.inSeconds ?? 0, durationInSeconds = remote.duration.toDuration()?.inSeconds ?? 0,
type = remote.type.toAssetType(), type = remote.type.toAssetType(),
fileName = remote.originalFileName, fileName = remote.originalFileName,
height = remote.exifInfo?.exifImageHeight?.toInt(), height = remote.exifInfo?.exifImageHeight?.toInt(),
width = remote.exifInfo?.exifImageWidth?.toInt(), width = remote.exifInfo?.exifImageWidth?.toInt(),
livePhotoVideoId = remote.livePhotoVideoId, livePhotoVideoId = remote.livePhotoVideoId,
ownerId = fastHash(remote.ownerId), ownerId = fastHash(remote.ownerId),
exifInfo = remote.exifInfo == null ? null : ExifDtoConverter.fromDto(remote.exifInfo!), exifInfo = remote.exifInfo == null ? null : ExifDtoConverter.fromDto(remote.exifInfo!),
isFavorite = remote.isFavorite, isFavorite = remote.isFavorite,
isArchived = remote.isArchived, isArchived = remote.isArchived,
isTrashed = remote.isTrashed, isTrashed = remote.isTrashed,
isOffline = remote.isOffline, isOffline = remote.isOffline,
// workaround to nullify stackPrimaryAssetId for the parent asset until we refactor the mobile app // workaround to nullify stackPrimaryAssetId for the parent asset until we refactor the mobile app
// stack handling to properly handle it // stack handling to properly handle it
stackPrimaryAssetId = remote.stack?.primaryAssetId == remote.id ? null : remote.stack?.primaryAssetId, stackPrimaryAssetId = remote.stack?.primaryAssetId == remote.id ? null : remote.stack?.primaryAssetId,
stackCount = remote.stack?.assetCount ?? 0, stackCount = remote.stack?.assetCount ?? 0,
stackId = remote.stack?.id, stackId = remote.stack?.id,
thumbhash = remote.thumbhash, thumbhash = remote.thumbhash,
visibility = getVisibility(remote.visibility); visibility = getVisibility(remote.visibility);
Asset({ Asset({
this.id = Isar.autoIncrement, this.id = Isar.autoIncrement,
@ -127,11 +127,7 @@ class Asset {
@Index(unique: false, replace: false, type: IndexType.hash) @Index(unique: false, replace: false, type: IndexType.hash)
String? localId; String? localId;
@Index( @Index(unique: true, replace: false, composite: [CompositeIndex("checksum", type: IndexType.hash)])
unique: true,
replace: false,
composite: [CompositeIndex("checksum", type: IndexType.hash)],
)
int ownerId; int ownerId;
DateTime fileCreatedAt; DateTime fileCreatedAt;
@ -447,33 +443,32 @@ class Asset {
int? stackCount, int? stackCount,
String? thumbhash, String? thumbhash,
AssetVisibilityEnum? visibility, AssetVisibilityEnum? visibility,
}) => }) => Asset(
Asset( id: id ?? this.id,
id: id ?? this.id, checksum: checksum ?? this.checksum,
checksum: checksum ?? this.checksum, remoteId: remoteId ?? this.remoteId,
remoteId: remoteId ?? this.remoteId, localId: localId ?? this.localId,
localId: localId ?? this.localId, ownerId: ownerId ?? this.ownerId,
ownerId: ownerId ?? this.ownerId, fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt,
fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt, fileModifiedAt: fileModifiedAt ?? this.fileModifiedAt,
fileModifiedAt: fileModifiedAt ?? this.fileModifiedAt, updatedAt: updatedAt ?? this.updatedAt,
updatedAt: updatedAt ?? this.updatedAt, durationInSeconds: durationInSeconds ?? this.durationInSeconds,
durationInSeconds: durationInSeconds ?? this.durationInSeconds, type: type ?? this.type,
type: type ?? this.type, width: width ?? this.width,
width: width ?? this.width, height: height ?? this.height,
height: height ?? this.height, fileName: fileName ?? this.fileName,
fileName: fileName ?? this.fileName, livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, isFavorite: isFavorite ?? this.isFavorite,
isFavorite: isFavorite ?? this.isFavorite, isArchived: isArchived ?? this.isArchived,
isArchived: isArchived ?? this.isArchived, isTrashed: isTrashed ?? this.isTrashed,
isTrashed: isTrashed ?? this.isTrashed, isOffline: isOffline ?? this.isOffline,
isOffline: isOffline ?? this.isOffline, exifInfo: exifInfo ?? this.exifInfo,
exifInfo: exifInfo ?? this.exifInfo, stackId: stackId ?? this.stackId,
stackId: stackId ?? this.stackId, stackPrimaryAssetId: stackPrimaryAssetId ?? this.stackPrimaryAssetId,
stackPrimaryAssetId: stackPrimaryAssetId ?? this.stackPrimaryAssetId, stackCount: stackCount ?? this.stackCount,
stackCount: stackCount ?? this.stackCount, thumbhash: thumbhash ?? this.thumbhash,
thumbhash: thumbhash ?? this.thumbhash, visibility: visibility ?? this.visibility,
visibility: visibility ?? this.visibility, );
);
Future<void> put(Isar db) async { Future<void> put(Isar db) async {
await db.assets.put(this); await db.assets.put(this);
@ -494,10 +489,7 @@ class Asset {
return compareByChecksum(a, b); return compareByChecksum(a, b);
} }
static int compareByOwnerChecksumCreatedModified( static int compareByOwnerChecksumCreatedModified(Asset a, Asset b) {
Asset a,
Asset b,
) {
final int ownerIdOrder = a.ownerId.compareTo(b.ownerId); final int ownerIdOrder = a.ownerId.compareTo(b.ownerId);
if (ownerIdOrder != 0) return ownerIdOrder; if (ownerIdOrder != 0) return ownerIdOrder;
final int checksumOrder = compareByChecksum(a, b); final int checksumOrder = compareByChecksum(a, b);
@ -539,11 +531,11 @@ class Asset {
} }
static getVisibility(AssetVisibility visibility) => switch (visibility) { static getVisibility(AssetVisibility visibility) => switch (visibility) {
AssetVisibility.archive => AssetVisibilityEnum.archive, AssetVisibility.archive => AssetVisibilityEnum.archive,
AssetVisibility.hidden => AssetVisibilityEnum.hidden, AssetVisibility.hidden => AssetVisibilityEnum.hidden,
AssetVisibility.locked => AssetVisibilityEnum.locked, AssetVisibility.locked => AssetVisibilityEnum.locked,
AssetVisibility.timeline || _ => AssetVisibilityEnum.timeline, AssetVisibility.timeline || _ => AssetVisibilityEnum.timeline,
}; };
} }
enum AssetType { enum AssetType {
@ -556,21 +548,17 @@ enum AssetType {
extension AssetTypeEnumHelper on AssetTypeEnum { extension AssetTypeEnumHelper on AssetTypeEnum {
AssetType toAssetType() => switch (this) { AssetType toAssetType() => switch (this) {
AssetTypeEnum.IMAGE => AssetType.image, AssetTypeEnum.IMAGE => AssetType.image,
AssetTypeEnum.VIDEO => AssetType.video, AssetTypeEnum.VIDEO => AssetType.video,
AssetTypeEnum.AUDIO => AssetType.audio, AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.OTHER => AssetType.other, AssetTypeEnum.OTHER => AssetType.other,
_ => throw Exception(), _ => throw Exception(),
}; };
} }
/// Describes where the information of this asset came from: /// Describes where the information of this asset came from:
/// only from the local device, only from the remote server or merged from both /// only from the local device, only from the remote server or merged from both
enum AssetState { enum AssetState { local, remote, merged }
local,
remote,
merged,
}
extension AssetsHelper on IsarCollection<Asset> { extension AssetsHelper on IsarCollection<Asset> {
Future<int> deleteAllByRemoteId(Iterable<String> ids) => ids.isEmpty ? Future.value(0) : remote(ids).deleteAll(); Future<int> deleteAllByRemoteId(Iterable<String> ids) => ids.isEmpty ? Future.value(0) : remote(ids).deleteAll();
@ -579,13 +567,9 @@ extension AssetsHelper on IsarCollection<Asset> {
Future<List<Asset>> getAllByLocalId(Iterable<String> ids) => ids.isEmpty ? Future.value([]) : local(ids).findAll(); Future<List<Asset>> getAllByLocalId(Iterable<String> ids) => ids.isEmpty ? Future.value([]) : local(ids).findAll();
Future<Asset?> getByRemoteId(String id) => where().remoteIdEqualTo(id).findFirst(); Future<Asset?> getByRemoteId(String id) => where().remoteIdEqualTo(id).findFirst();
QueryBuilder<Asset, Asset, QAfterWhereClause> remote( QueryBuilder<Asset, Asset, QAfterWhereClause> remote(Iterable<String> ids) =>
Iterable<String> ids,
) =>
where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e)); where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e));
QueryBuilder<Asset, Asset, QAfterWhereClause> local( QueryBuilder<Asset, Asset, QAfterWhereClause> local(Iterable<String> ids) {
Iterable<String> ids,
) {
return where().anyOf(ids, (q, String e) => q.localIdEqualTo(e)); return where().anyOf(ids, (q, String e) => q.localIdEqualTo(e));
} }
} }

Binary file not shown.

View file

@ -14,21 +14,9 @@ class BackupAlbum {
Id get isarId => fastHash(id); Id get isarId => fastHash(id);
BackupAlbum copyWith({ BackupAlbum copyWith({String? id, DateTime? lastBackup, BackupSelection? selection}) {
String? id, return BackupAlbum(id ?? this.id, lastBackup ?? this.lastBackup, selection ?? this.selection);
DateTime? lastBackup,
BackupSelection? selection,
}) {
return BackupAlbum(
id ?? this.id,
lastBackup ?? this.lastBackup,
selection ?? this.selection,
);
} }
} }
enum BackupSelection { enum BackupSelection { none, select, exclude }
none,
select,
exclude;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -15,16 +15,10 @@ extension TZExtension on Asset {
final location = getLocation(exifInfo!.timeZone!); final location = getLocation(exifInfo!.timeZone!);
dt = TZDateTime.from(dt, location); dt = TZDateTime.from(dt, location);
} on LocationNotFoundException { } on LocationNotFoundException {
RegExp re = RegExp( RegExp re = RegExp(r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$', caseSensitive: false);
r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$',
caseSensitive: false,
);
final m = re.firstMatch(exifInfo!.timeZone!); final m = re.firstMatch(exifInfo!.timeZone!);
if (m != null) { if (m != null) {
final duration = Duration( final duration = Duration(hours: int.parse(m.group(1) ?? '0'), minutes: int.parse(m.group(2) ?? '0'));
hours: int.parse(m.group(1) ?? '0'),
minutes: int.parse(m.group(2) ?? '0'),
);
dt = dt.add(duration); dt = dt.add(duration);
return (dt, duration); return (dt, duration);
} }

View file

@ -6,10 +6,7 @@ import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/utils/hash.dart'; import 'package:immich_mobile/utils/hash.dart';
extension ListExtension<E> on List<E> { extension ListExtension<E> on List<E> {
List<E> uniqueConsecutive({ List<E> uniqueConsecutive({int Function(E a, E b)? compare, void Function(E a, E b)? onDuplicate}) {
int Function(E a, E b)? compare,
void Function(E a, E b)? onDuplicate,
}) {
compare ??= (E a, E b) => a == b ? 0 : 1; compare ??= (E a, E b) => a == b ? 0 : 1;
int i = 1, j = 1; int i = 1, j = 1;
for (; i < length; i++) { for (; i < length; i++) {
@ -45,9 +42,7 @@ extension IntListExtension on Iterable<int> {
extension AssetListExtension on Iterable<Asset> { extension AssetListExtension on Iterable<Asset> {
/// Returns the assets that are already available in the Immich server /// Returns the assets that are already available in the Immich server
Iterable<Asset> remoteOnly({ Iterable<Asset> remoteOnly({void Function()? errorCallback}) {
void Function()? errorCallback,
}) {
final bool onlyRemote = every((e) => e.isRemote); final bool onlyRemote = every((e) => e.isRemote);
if (!onlyRemote) { if (!onlyRemote) {
if (errorCallback != null) errorCallback(); if (errorCallback != null) errorCallback();
@ -58,10 +53,7 @@ extension AssetListExtension on Iterable<Asset> {
/// Returns the assets that are owned by the user passed to the [owner] param /// Returns the assets that are owned by the user passed to the [owner] param
/// If [owner] is null, an empty list is returned /// If [owner] is null, an empty list is returned
Iterable<Asset> ownedOnly( Iterable<Asset> ownedOnly(UserDto? owner, {void Function()? errorCallback}) {
UserDto? owner, {
void Function()? errorCallback,
}) {
if (owner == null) return []; if (owner == null) return [];
final isarUserId = fastHash(owner.id); final isarUserId = fastHash(owner.id);
final bool onlyOwned = every((e) => e.ownerId == isarUserId); final bool onlyOwned = every((e) => e.ownerId == isarUserId);

View file

@ -47,11 +47,7 @@ extension DateRangeFormatting on DateTime {
/// - Date range of this year: "Mar 23-May 31" /// - Date range of this year: "Mar 23-May 31"
/// - Date range of other year: "Aug 28 - Sep 30, 2023" /// - Date range of other year: "Aug 28 - Sep 30, 2023"
/// - Date range over multiple years: "Apr 17, 2021 - Apr 9, 2022" /// - Date range over multiple years: "Apr 17, 2021 - Apr 9, 2022"
static String formatDateRange( static String formatDateRange(DateTime startDate, DateTime endDate, Locale? locale) {
DateTime startDate,
DateTime endDate,
Locale? locale,
) {
final now = DateTime.now(); final now = DateTime.now();
final currentYear = now.year; final currentYear = now.year;
final localeString = locale?.toString() ?? 'en_US'; final localeString = locale?.toString() ?? 'en_US';

View file

@ -13,9 +13,7 @@ extension MapMarkers on MapLibreMapController {
Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async { Future<void> addGeoJSONSourceForMarkers(List<MapMarker> markers) async {
return addSource( return addSource(
MapUtils.defaultSourceId, MapUtils.defaultSourceId,
GeojsonSourceProperties( GeojsonSourceProperties(data: MapUtils.generateGeoJsonForMarkers(markers.toList())),
data: MapUtils.generateGeoJsonForMarkers(markers.toList()),
),
); );
} }
@ -73,23 +71,13 @@ extension MapMarkers on MapLibreMapController {
try { try {
final ByteData bytes = await rootBundle.load("assets/location-pin.png"); final ByteData bytes = await rootBundle.load("assets/location-pin.png");
await addImage("mapMarker", bytes.buffer.asUint8List()); await addImage("mapMarker", bytes.buffer.asUint8List());
return addSymbol( return addSymbol(SymbolOptions(geometry: centre, iconImage: "mapMarker", iconSize: 0.15, iconAnchor: "bottom"));
SymbolOptions(
geometry: centre,
iconImage: "mapMarker",
iconSize: 0.15,
iconAnchor: "bottom",
),
);
} finally { } finally {
// no-op // no-op
} }
} }
Future<LatLngBounds> getBoundsFromPoint( Future<LatLngBounds> getBoundsFromPoint(Point<double> point, double distance) async {
Point<double> point,
double distance,
) async {
final southWestPx = Point(point.x - distance, point.y + distance); final southWestPx = Point(point.x - distance, point.y + distance);
final northEastPx = Point(point.x + distance, point.y - distance); final northEastPx = Point(point.x + distance, point.y - distance);

View file

@ -10,11 +10,7 @@ class FastScrollPhysics extends ScrollPhysics {
} }
@override @override
SpringDescription get spring => const SpringDescription( SpringDescription get spring => const SpringDescription(mass: 1, stiffness: 402.49984375, damping: 40);
mass: 1,
stiffness: 402.49984375,
damping: 40,
);
} }
class FastClampingScrollPhysics extends ClampingScrollPhysics { class FastClampingScrollPhysics extends ClampingScrollPhysics {
@ -27,12 +23,12 @@ class FastClampingScrollPhysics extends ClampingScrollPhysics {
@override @override
SpringDescription get spring => const SpringDescription( SpringDescription get spring => const SpringDescription(
// When swiping between videos on Android, the placeholder of the first opened video // When swiping between videos on Android, the placeholder of the first opened video
// can briefly be seen and cause a flicker effect if the video begins to initialize // can briefly be seen and cause a flicker effect if the video begins to initialize
// before the animation finishes - probably a bug in PhotoViewGallery's animation handling // before the animation finishes - probably a bug in PhotoViewGallery's animation handling
// Making the animation faster is not just stylistic, but also helps to avoid this flicker // Making the animation faster is not just stylistic, but also helps to avoid this flicker
mass: 1, mass: 1,
stiffness: 1601.2499609375, stiffness: 1601.2499609375,
damping: 80, damping: 80,
); );
} }

View file

@ -1,10 +1,6 @@
extension StringExtension on String { extension StringExtension on String {
String capitalize() { String capitalize() {
return split(" ") return split(" ").map((str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1)).join(" ");
.map(
(str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1),
)
.join(" ");
} }
} }

View file

@ -7,16 +7,10 @@ extension ImmichColorSchemeExtensions on ColorScheme {
extension ColorExtensions on Color { extension ColorExtensions on Color {
Color lighten({double amount = 0.1}) { Color lighten({double amount = 0.1}) {
return Color.alphaBlend( return Color.alphaBlend(Colors.white.withValues(alpha: amount), this);
Colors.white.withValues(alpha: amount),
this,
);
} }
Color darken({double amount = 0.1}) { Color darken({double amount = 0.1}) {
return Color.alphaBlend( return Color.alphaBlend(Colors.black.withValues(alpha: amount), this);
Colors.black.withValues(alpha: amount),
this,
);
} }
} }

View file

@ -29,11 +29,7 @@ extension TextTranslateExtension on Text {
} }
} }
String _translateHelper( String _translateHelper(BuildContext? context, String key, [Map<String, Object>? args]) {
BuildContext? context,
String key, [
Map<String, Object>? args,
]) {
if (key.isEmpty) { if (key.isEmpty) {
return ''; return '';
} }

View file

@ -16,21 +16,10 @@ class DeviceAssetEntity {
final List<byte> hash; final List<byte> hash;
final DateTime modifiedTime; final DateTime modifiedTime;
const DeviceAssetEntity({ const DeviceAssetEntity({required this.assetId, required this.hash, required this.modifiedTime});
required this.assetId,
required this.hash,
required this.modifiedTime,
});
DeviceAsset toModel() => DeviceAsset( DeviceAsset toModel() => DeviceAsset(assetId: assetId, hash: Uint8List.fromList(hash), modifiedTime: modifiedTime);
assetId: assetId,
hash: Uint8List.fromList(hash),
modifiedTime: modifiedTime,
);
static DeviceAssetEntity fromDto(DeviceAsset dto) => DeviceAssetEntity( static DeviceAssetEntity fromDto(DeviceAsset dto) =>
assetId: dto.assetId, DeviceAssetEntity(assetId: dto.assetId, hash: dto.hash, modifiedTime: dto.modifiedTime);
hash: dto.hash,
modifiedTime: dto.modifiedTime,
);
} }

View file

@ -52,47 +52,47 @@ class ExifInfo {
}); });
static ExifInfo fromDto(domain.ExifInfo dto) => ExifInfo( static ExifInfo fromDto(domain.ExifInfo dto) => ExifInfo(
id: dto.assetId, id: dto.assetId,
fileSize: dto.fileSize, fileSize: dto.fileSize,
dateTimeOriginal: dto.dateTimeOriginal, dateTimeOriginal: dto.dateTimeOriginal,
timeZone: dto.timeZone, timeZone: dto.timeZone,
make: dto.make, make: dto.make,
model: dto.model, model: dto.model,
lens: dto.lens, lens: dto.lens,
f: dto.f, f: dto.f,
mm: dto.mm, mm: dto.mm,
iso: dto.iso?.toInt(), iso: dto.iso?.toInt(),
exposureSeconds: dto.exposureSeconds, exposureSeconds: dto.exposureSeconds,
lat: dto.latitude, lat: dto.latitude,
long: dto.longitude, long: dto.longitude,
city: dto.city, city: dto.city,
state: dto.state, state: dto.state,
country: dto.country, country: dto.country,
description: dto.description, description: dto.description,
orientation: dto.orientation, orientation: dto.orientation,
); );
domain.ExifInfo toDto() => domain.ExifInfo( domain.ExifInfo toDto() => domain.ExifInfo(
assetId: id, assetId: id,
fileSize: fileSize, fileSize: fileSize,
description: description, description: description,
orientation: orientation, orientation: orientation,
timeZone: timeZone, timeZone: timeZone,
dateTimeOriginal: dateTimeOriginal, dateTimeOriginal: dateTimeOriginal,
isFlipped: ExifDtoConverter.isOrientationFlipped(orientation), isFlipped: ExifDtoConverter.isOrientationFlipped(orientation),
latitude: lat, latitude: lat,
longitude: long, longitude: long,
city: city, city: city,
state: state, state: state,
country: country, country: country,
make: make, make: make,
model: model, model: model,
lens: lens, lens: lens,
f: f, f: f,
mm: mm, mm: mm,
iso: iso?.toInt(), iso: iso?.toInt(),
exposureSeconds: exposureSeconds, exposureSeconds: exposureSeconds,
); );
} }
class RemoteExifEntity extends Table with DriftDefaultsMixin { class RemoteExifEntity extends Table with DriftDefaultsMixin {
@ -148,24 +148,24 @@ class RemoteExifEntity extends Table with DriftDefaultsMixin {
extension RemoteExifEntityDataDomainEx on RemoteExifEntityData { extension RemoteExifEntityDataDomainEx on RemoteExifEntityData {
domain.ExifInfo toDto() => domain.ExifInfo( domain.ExifInfo toDto() => domain.ExifInfo(
fileSize: fileSize, fileSize: fileSize,
dateTimeOriginal: dateTimeOriginal, dateTimeOriginal: dateTimeOriginal,
timeZone: timeZone, timeZone: timeZone,
make: make, make: make,
model: model, model: model,
iso: iso, iso: iso,
city: city, city: city,
state: state, state: state,
country: country, country: country,
description: description, description: description,
orientation: orientation, orientation: orientation,
latitude: latitude, latitude: latitude,
longitude: longitude, longitude: longitude,
f: fNumber?.toDouble(), f: fNumber?.toDouble(),
mm: focalLength?.toDouble(), mm: focalLength?.toDouble(),
lens: lens, lens: lens,
width: width?.toDouble(), width: width?.toDouble(),
height: height?.toDouble(), height: height?.toDouble(),
isFlipped: ExifDtoConverter.isOrientationFlipped(orientation), isFlipped: ExifDtoConverter.isOrientationFlipped(orientation),
); );
} }

View file

@ -22,17 +22,17 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
extension LocalAssetEntityDataDomainEx on LocalAssetEntityData { extension LocalAssetEntityDataDomainEx on LocalAssetEntityData {
LocalAsset toDto() => LocalAsset( LocalAsset toDto() => LocalAsset(
id: id, id: id,
name: name, name: name,
checksum: checksum, checksum: checksum,
type: type, type: type,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
durationInSeconds: durationInSeconds, durationInSeconds: durationInSeconds,
isFavorite: isFavorite, isFavorite: isFavorite,
height: height, height: height,
width: width, width: width,
remoteId: null, remoteId: null,
orientation: orientation, orientation: orientation,
); );
} }

View file

@ -5,11 +5,7 @@ import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart'; import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
@TableIndex( @TableIndex(name: 'UQ_remote_asset_owner_checksum', columns: {#checksum, #ownerId}, unique: true)
name: 'UQ_remote_asset_owner_checksum',
columns: {#checksum, #ownerId},
unique: true,
)
@TableIndex(name: 'idx_remote_asset_checksum', columns: {#checksum}) @TableIndex(name: 'idx_remote_asset_checksum', columns: {#checksum})
class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin { class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
const RemoteAssetEntity(); const RemoteAssetEntity();
@ -40,21 +36,21 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin
extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
RemoteAsset toDto() => RemoteAsset( RemoteAsset toDto() => RemoteAsset(
id: id, id: id,
name: name, name: name,
ownerId: ownerId, ownerId: ownerId,
checksum: checksum, checksum: checksum,
type: type, type: type,
createdAt: createdAt, createdAt: createdAt,
updatedAt: updatedAt, updatedAt: updatedAt,
durationInSeconds: durationInSeconds, durationInSeconds: durationInSeconds,
isFavorite: isFavorite, isFavorite: isFavorite,
height: height, height: height,
width: width, width: width,
thumbHash: thumbHash, thumbHash: thumbHash,
visibility: visibility, visibility: visibility,
livePhotoVideoId: livePhotoVideoId, livePhotoVideoId: livePhotoVideoId,
localId: null, localId: null,
stackId: stackId, stackId: stackId,
); );
} }

View file

@ -43,36 +43,36 @@ class User {
}); });
static User fromDto(UserDto dto) => User( static User fromDto(UserDto dto) => User(
id: dto.id, id: dto.id,
updatedAt: dto.updatedAt, updatedAt: dto.updatedAt,
email: dto.email, email: dto.email,
name: dto.name, name: dto.name,
isAdmin: dto.isAdmin, isAdmin: dto.isAdmin,
isPartnerSharedBy: dto.isPartnerSharedBy, isPartnerSharedBy: dto.isPartnerSharedBy,
isPartnerSharedWith: dto.isPartnerSharedWith, isPartnerSharedWith: dto.isPartnerSharedWith,
profileImagePath: dto.profileImagePath ?? "", profileImagePath: dto.profileImagePath ?? "",
avatarColor: dto.avatarColor, avatarColor: dto.avatarColor,
memoryEnabled: dto.memoryEnabled, memoryEnabled: dto.memoryEnabled,
inTimeline: dto.inTimeline, inTimeline: dto.inTimeline,
quotaUsageInBytes: dto.quotaUsageInBytes, quotaUsageInBytes: dto.quotaUsageInBytes,
quotaSizeInBytes: dto.quotaSizeInBytes, quotaSizeInBytes: dto.quotaSizeInBytes,
); );
UserDto toDto() => UserDto( UserDto toDto() => UserDto(
id: id, id: id,
email: email, email: email,
name: name, name: name,
isAdmin: isAdmin, isAdmin: isAdmin,
updatedAt: updatedAt, updatedAt: updatedAt,
profileImagePath: profileImagePath.isEmpty ? null : profileImagePath, profileImagePath: profileImagePath.isEmpty ? null : profileImagePath,
avatarColor: avatarColor, avatarColor: avatarColor,
memoryEnabled: memoryEnabled, memoryEnabled: memoryEnabled,
inTimeline: inTimeline, inTimeline: inTimeline,
isPartnerSharedBy: isPartnerSharedBy, isPartnerSharedBy: isPartnerSharedBy,
isPartnerSharedWith: isPartnerSharedWith, isPartnerSharedWith: isPartnerSharedWith,
quotaUsageInBytes: quotaUsageInBytes, quotaUsageInBytes: quotaUsageInBytes,
quotaSizeInBytes: quotaSizeInBytes, quotaSizeInBytes: quotaSizeInBytes,
); );
} }
class UserEntity extends Table with DriftDefaultsMixin { class UserEntity extends Table with DriftDefaultsMixin {

View file

@ -6,21 +6,13 @@ import 'package:photo_manager/photo_manager.dart';
class AssetMediaRepository { class AssetMediaRepository {
const AssetMediaRepository(); const AssetMediaRepository();
Future<Uint8List?> getThumbnail( Future<Uint8List?> getThumbnail(String id, {int quality = 80, Size size = const Size.square(256)}) => AssetEntity(
String id, { id: id,
int quality = 80, // The below fields are not used in thumbnailDataWithSize but are required
Size size = const Size.square(256), // to create an AssetEntity instance. It is faster to create a dummy AssetEntity
}) => // instance than to fetch the asset from the device first.
AssetEntity( typeInt: AssetType.image.index,
id: id, width: size.width.toInt(),
// The below fields are not used in thumbnailDataWithSize but are required height: size.height.toInt(),
// to create an AssetEntity instance. It is faster to create a dummy AssetEntity ).thumbnailDataWithSize(ThumbnailSize(size.width.toInt(), size.height.toInt()), quality: quality);
// instance than to fetch the asset from the device first.
typeInt: AssetType.image.index,
width: size.width.toInt(),
height: size.height.toInt(),
).thumbnailDataWithSize(
ThumbnailSize(size.width.toInt(), size.height.toInt()),
quality: quality,
);
} }

View file

@ -24,9 +24,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
useColumns: false, useColumns: false,
), ),
]) ])
..where( ..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.excluded));
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.excluded),
);
} }
Future<int> getTotalCount() async { Future<int> getTotalCount() async {
@ -79,9 +77,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
Future<int> getBackupCount(String userId) async { Future<int> getBackupCount(String userId) async {
final query = _db.localAlbumAssetEntity.selectOnly(distinct: true) final query = _db.localAlbumAssetEntity.selectOnly(distinct: true)
..addColumns( ..addColumns([_db.localAlbumAssetEntity.assetId])
[_db.localAlbumAssetEntity.assetId],
)
..join([ ..join([
innerJoin( innerJoin(
_db.localAlbumEntity, _db.localAlbumEntity,
@ -112,9 +108,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
Future<List<LocalAsset>> getCandidates(String userId) async { Future<List<LocalAsset>> getCandidates(String userId) async {
final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true) final selectedAlbumIds = _db.localAlbumEntity.selectOnly(distinct: true)
..addColumns([_db.localAlbumEntity.id]) ..addColumns([_db.localAlbumEntity.id])
..where( ..where(_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected));
_db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected),
);
final query = _db.localAssetEntity.select() final query = _db.localAssetEntity.select()
..where( ..where(
@ -138,11 +132,7 @@ class DriftBackupRepository extends DriftDatabaseRepository {
) & ) &
lae.id.isNotInQuery(_getExcludedSubquery()), lae.id.isNotInQuery(_getExcludedSubquery()),
) )
..orderBy( ..orderBy([(localAsset) => OrderingTerm.desc(localAsset.createdAt)]);
[
(localAsset) => OrderingTerm.desc(localAsset.createdAt),
],
);
return query.map((localAsset) => localAsset.toDto()).get(); return query.map((localAsset) => localAsset.toDto()).get();
} }

View file

@ -59,66 +59,58 @@ class IsarDatabaseRepository implements IDatabaseRepository {
PersonEntity, PersonEntity,
AssetFaceEntity, AssetFaceEntity,
], ],
include: { include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
'package:immich_mobile/infrastructure/entities/merged_asset.drift',
},
) )
class Drift extends $Drift implements IDatabaseRepository { class Drift extends $Drift implements IDatabaseRepository {
Drift([QueryExecutor? executor]) Drift([QueryExecutor? executor])
: super( : super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true)));
executor ??
driftDatabase(
name: 'immich',
native: const DriftNativeOptions(shareAcrossIsolates: true),
),
);
@override @override
int get schemaVersion => 4; int get schemaVersion => 4;
@override @override
MigrationStrategy get migration => MigrationStrategy( MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (m, from, to) async { onUpgrade: (m, from, to) async {
// Run migration steps without foreign keys and re-enable them later // Run migration steps without foreign keys and re-enable them later
await customStatement('PRAGMA foreign_keys = OFF'); await customStatement('PRAGMA foreign_keys = OFF');
await m.runMigrationSteps( await m.runMigrationSteps(
from: from, from: from,
to: to, to: to,
steps: migrationSteps( steps: migrationSteps(
from1To2: (m, v2) async { from1To2: (m, v2) async {
for (final entity in v2.entities) { for (final entity in v2.entities) {
await m.drop(entity); await m.drop(entity);
await m.create(entity); await m.create(entity);
} }
}, },
from2To3: (m, v3) async { from2To3: (m, v3) async {
// Removed foreign key constraint on stack.primaryAssetId // Removed foreign key constraint on stack.primaryAssetId
await m.alterTable(TableMigration(v3.stackEntity)); await m.alterTable(TableMigration(v3.stackEntity));
}, },
from3To4: (m, v4) async { from3To4: (m, v4) async {
// Thumbnail path column got removed from person_entity // Thumbnail path column got removed from person_entity
await m.alterTable(TableMigration(v4.personEntity)); await m.alterTable(TableMigration(v4.personEntity));
// asset_face_entity is added // asset_face_entity is added
await m.create(v4.assetFaceEntity); await m.create(v4.assetFaceEntity);
}, },
), ),
);
if (kDebugMode) {
// Fail if the migration broke foreign keys
final wrongFKs = await customSelect('PRAGMA foreign_key_check').get();
assert(wrongFKs.isEmpty, '${wrongFKs.map((e) => e.data)}');
}
await customStatement('PRAGMA foreign_keys = ON;');
},
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
await customStatement('PRAGMA synchronous = NORMAL');
await customStatement('PRAGMA journal_mode = WAL');
},
); );
if (kDebugMode) {
// Fail if the migration broke foreign keys
final wrongFKs = await customSelect('PRAGMA foreign_key_check').get();
assert(wrongFKs.isEmpty, '${wrongFKs.map((e) => e.data)}');
}
await customStatement('PRAGMA foreign_keys = ON;');
},
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
await customStatement('PRAGMA synchronous = NORMAL');
await customStatement('PRAGMA journal_mode = WAL');
},
);
} }
class DriftDatabaseRepository implements IDatabaseRepository { class DriftDatabaseRepository implements IDatabaseRepository {

View file

@ -33,9 +33,7 @@ class IsarExifRepository extends IsarDatabaseRepository {
Future<List<ExifInfo>> updateAll(List<ExifInfo> exifInfos) { Future<List<ExifInfo>> updateAll(List<ExifInfo> exifInfos) {
return transaction(() async { return transaction(() async {
await _db.exifInfos.putAll( await _db.exifInfos.putAll(exifInfos.map(entity.ExifInfo.fromDto).toList());
exifInfos.map(entity.ExifInfo.fromDto).toList(),
);
return exifInfos; return exifInfos;
}); });
} }

View file

@ -14,8 +14,8 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
final Drift _db; final Drift _db;
final Platform _platform; final Platform _platform;
const DriftLocalAlbumRepository(this._db, {Platform? platform}) const DriftLocalAlbumRepository(this._db, {Platform? platform})
: _platform = platform ?? const LocalPlatform(), : _platform = platform ?? const LocalPlatform(),
super(_db); super(_db);
Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) { Future<List<LocalAlbum>> getAll({Set<SortLocalAlbumsBy> sortBy = const {}}) {
final assetCount = _db.localAlbumAssetEntity.assetId.count(); final assetCount = _db.localAlbumAssetEntity.assetId.count();
@ -34,41 +34,32 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
if (sortBy.isNotEmpty) { if (sortBy.isNotEmpty) {
final orderings = <OrderingTerm>[]; final orderings = <OrderingTerm>[];
for (final sort in sortBy) { for (final sort in sortBy) {
orderings.add( orderings.add(switch (sort) {
switch (sort) { SortLocalAlbumsBy.id => OrderingTerm.asc(_db.localAlbumEntity.id),
SortLocalAlbumsBy.id => OrderingTerm.asc(_db.localAlbumEntity.id), SortLocalAlbumsBy.backupSelection => OrderingTerm.asc(_db.localAlbumEntity.backupSelection),
SortLocalAlbumsBy.backupSelection => OrderingTerm.asc(_db.localAlbumEntity.backupSelection), SortLocalAlbumsBy.isIosSharedAlbum => OrderingTerm.asc(_db.localAlbumEntity.isIosSharedAlbum),
SortLocalAlbumsBy.isIosSharedAlbum => OrderingTerm.asc(_db.localAlbumEntity.isIosSharedAlbum), SortLocalAlbumsBy.name => OrderingTerm.asc(_db.localAlbumEntity.name),
SortLocalAlbumsBy.name => OrderingTerm.asc(_db.localAlbumEntity.name), SortLocalAlbumsBy.assetCount => OrderingTerm.desc(assetCount),
SortLocalAlbumsBy.assetCount => OrderingTerm.desc(assetCount), });
},
);
} }
query.orderBy(orderings); query.orderBy(orderings);
} }
return query return query.map((row) => row.readTable(_db.localAlbumEntity).toDto(assetCount: row.read(assetCount) ?? 0)).get();
.map(
(row) => row.readTable(_db.localAlbumEntity).toDto(assetCount: row.read(assetCount) ?? 0),
)
.get();
} }
Future<void> delete(String albumId) => transaction(() async { Future<void> delete(String albumId) => transaction(() async {
// Remove all assets that are only in this particular album // Remove all assets that are only in this particular album
// We cannot remove all assets in the album because they might be in other albums in iOS // We cannot remove all assets in the album because they might be in other albums in iOS
// That is not the case on Android since asset <-> album has one:one mapping // That is not the case on Android since asset <-> album has one:one mapping
final assetsToDelete = _platform.isIOS ? await _getUniqueAssetsInAlbum(albumId) : await getAssetIds(albumId); final assetsToDelete = _platform.isIOS ? await _getUniqueAssetsInAlbum(albumId) : await getAssetIds(albumId);
await _deleteAssets(assetsToDelete); await _deleteAssets(assetsToDelete);
// All the other assets that are still associated will be unlinked automatically on-cascade // All the other assets that are still associated will be unlinked automatically on-cascade
await _db.managers.localAlbumEntity.filter((a) => a.id.equals(albumId)).delete(); await _db.managers.localAlbumEntity.filter((a) => a.id.equals(albumId)).delete();
}); });
Future<void> syncDeletes( Future<void> syncDeletes(String albumId, Iterable<String> assetIdsToKeep) async {
String albumId,
Iterable<String> assetIdsToKeep,
) async {
if (assetIdsToKeep.isEmpty) { if (assetIdsToKeep.isEmpty) {
return Future.value(); return Future.value();
} }
@ -77,12 +68,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
deleteSmt.where((localAsset) { deleteSmt.where((localAsset) {
final subQuery = _db.localAlbumAssetEntity.selectOnly() final subQuery = _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId]) ..addColumns([_db.localAlbumAssetEntity.assetId])
..join([ ..join([innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id))]);
innerJoin(
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
),
]);
subQuery.where( subQuery.where(
_db.localAlbumEntity.id.equals(albumId) & _db.localAlbumAssetEntity.assetId.isNotIn(assetIdsToKeep), _db.localAlbumEntity.id.equals(albumId) & _db.localAlbumAssetEntity.assetId.isNotIn(assetIdsToKeep),
); );
@ -109,12 +95,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
if (toUpsert.isNotEmpty) { if (toUpsert.isNotEmpty) {
await _upsertAssets(toUpsert); await _upsertAssets(toUpsert);
await _db.localAlbumAssetEntity.insertAll( await _db.localAlbumAssetEntity.insertAll(
toUpsert.map( toUpsert.map((a) => LocalAlbumAssetEntityCompanion.insert(assetId: a.id, albumId: localAlbum.id)),
(a) => LocalAlbumAssetEntityCompanion.insert(
assetId: a.id,
albumId: localAlbum.id,
),
),
mode: InsertMode.insertOrIgnore, mode: InsertMode.insertOrIgnore,
); );
} }
@ -162,10 +143,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
final subQuery = _db.localAlbumAssetEntity.selectOnly() final subQuery = _db.localAlbumAssetEntity.selectOnly()
..addColumns([_db.localAlbumAssetEntity.assetId]) ..addColumns([_db.localAlbumAssetEntity.assetId])
..join([ ..join([
innerJoin( innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)),
_db.localAlbumEntity,
_db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id),
),
]); ]);
subQuery.where(_db.localAlbumEntity.marker_.isNotNull()); subQuery.where(_db.localAlbumEntity.marker_.isNotNull());
return localAsset.id.isInQuery(subQuery); return localAsset.id.isInQuery(subQuery);
@ -178,16 +156,12 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
} }
Future<List<LocalAsset>> getAssets(String albumId) { Future<List<LocalAsset>> getAssets(String albumId) {
final query = _db.localAlbumAssetEntity.select().join( final query =
[ _db.localAlbumAssetEntity.select().join([
innerJoin( innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
_db.localAssetEntity, ])
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), ..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
), ..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]);
],
)
..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]);
return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get(); return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
} }
@ -224,11 +198,8 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
batch.insertAll( batch.insertAll(
_db.localAlbumAssetEntity, _db.localAlbumAssetEntity,
albumIds.cast<String?>().nonNulls.map( albumIds.cast<String?>().nonNulls.map(
(albumId) => LocalAlbumAssetEntityCompanion.insert( (albumId) => LocalAlbumAssetEntityCompanion.insert(assetId: assetId, albumId: albumId),
assetId: assetId, ),
albumId: albumId,
),
),
onConflict: DoNothing(), onConflict: DoNothing(),
); );
}); });
@ -237,18 +208,12 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
} }
Future<List<LocalAsset>> getAssetsToHash(String albumId) { Future<List<LocalAsset>> getAssetsToHash(String albumId) {
final query = _db.localAlbumAssetEntity.select().join( final query =
[ _db.localAlbumAssetEntity.select().join([
innerJoin( innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
_db.localAssetEntity, ])
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), ..where(_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAssetEntity.checksum.isNull())
), ..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]);
],
)
..where(
_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAssetEntity.checksum.isNull(),
)
..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]);
return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get(); return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
} }
@ -275,10 +240,7 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>( batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
_db.localAssetEntity, _db.localAssetEntity,
companion, companion,
onConflict: DoUpdate( onConflict: DoUpdate((_) => companion, where: (old) => old.updatedAt.isNotValue(asset.updatedAt)),
(_) => companion,
where: (old) => old.updatedAt.isNotValue(asset.updatedAt),
),
); );
} }
}); });
@ -351,15 +313,13 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
} }
Future<LocalAsset?> getThumbnail(String albumId) async { Future<LocalAsset?> getThumbnail(String albumId) async {
final query = _db.localAlbumAssetEntity.select().join([ final query =
innerJoin( _db.localAlbumAssetEntity.select().join([
_db.localAssetEntity, innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), ])
), ..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
]) ..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)])
..where(_db.localAlbumAssetEntity.albumId.equals(albumId)) ..limit(1);
..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)])
..limit(1);
final results = await query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get(); final results = await query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();

View file

@ -16,14 +16,11 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum),
useColumns: false, useColumns: false,
), ),
]) ])..where(_db.localAssetEntity.id.equals(id));
..where(_db.localAssetEntity.id.equals(id));
return query.map((row) { return query.map((row) {
final asset = row.readTable(_db.localAssetEntity).toDto(); final asset = row.readTable(_db.localAssetEntity).toDto();
return asset.copyWith( return asset.copyWith(remoteId: row.read(_db.remoteAssetEntity.id));
remoteId: row.read(_db.remoteAssetEntity.id),
);
}).watchSingleOrNull(); }).watchSingleOrNull();
} }

View file

@ -13,30 +13,21 @@ class DriftMemoryRepository extends DriftDatabaseRepository {
final now = DateTime.now(); final now = DateTime.now();
final localUtc = DateTime.utc(now.year, now.month, now.day, 0, 0, 0); final localUtc = DateTime.utc(now.year, now.month, now.day, 0, 0, 0);
final query = _db.select(_db.memoryEntity).join([ final query =
leftOuterJoin( _db.select(_db.memoryEntity).join([
_db.memoryAssetEntity, leftOuterJoin(_db.memoryAssetEntity, _db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id)),
_db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id), leftOuterJoin(
), _db.remoteAssetEntity,
leftOuterJoin( _db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) &
_db.remoteAssetEntity, _db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline),
_db.remoteAssetEntity.deletedAt.isNull() & ),
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline), ])
), ..where(_db.memoryEntity.ownerId.equals(ownerId))
]) ..where(_db.memoryEntity.deletedAt.isNull())
..where(_db.memoryEntity.ownerId.equals(ownerId)) ..where(_db.memoryEntity.showAt.isSmallerOrEqualValue(localUtc))
..where(_db.memoryEntity.deletedAt.isNull()) ..where(_db.memoryEntity.hideAt.isBiggerOrEqualValue(localUtc))
..where( ..orderBy([OrderingTerm.desc(_db.memoryEntity.memoryAt), OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]);
_db.memoryEntity.showAt.isSmallerOrEqualValue(localUtc),
)
..where(
_db.memoryEntity.hideAt.isBiggerOrEqualValue(localUtc),
)
..orderBy([
OrderingTerm.desc(_db.memoryEntity.memoryAt),
OrderingTerm.asc(_db.remoteAssetEntity.createdAt),
]);
final rows = await query.get(); final rows = await query.get();
@ -59,24 +50,19 @@ class DriftMemoryRepository extends DriftDatabaseRepository {
} }
Future<DriftMemory?> get(String memoryId) async { Future<DriftMemory?> get(String memoryId) async {
final query = _db.select(_db.memoryEntity).join([ final query =
leftOuterJoin( _db.select(_db.memoryEntity).join([
_db.memoryAssetEntity, leftOuterJoin(_db.memoryAssetEntity, _db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id)),
_db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id), leftOuterJoin(
), _db.remoteAssetEntity,
leftOuterJoin( _db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) &
_db.remoteAssetEntity, _db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline),
_db.remoteAssetEntity.deletedAt.isNull() & ),
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline), ])
), ..where(_db.memoryEntity.id.equals(memoryId))
]) ..where(_db.memoryEntity.deletedAt.isNull())
..where(_db.memoryEntity.id.equals(memoryId)) ..orderBy([OrderingTerm.desc(_db.memoryEntity.memoryAt), OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]);
..where(_db.memoryEntity.deletedAt.isNull())
..orderBy([
OrderingTerm.desc(_db.memoryEntity.memoryAt),
OrderingTerm.asc(_db.remoteAssetEntity.createdAt),
]);
final rows = await query.get(); final rows = await query.get();

View file

@ -9,24 +9,13 @@ class DriftPartnerRepository extends DriftDatabaseRepository {
Future<List<PartnerUserDto>> getPartners(String userId) { Future<List<PartnerUserDto>> getPartners(String userId) {
final query = _db.select(_db.partnerEntity).join([ final query = _db.select(_db.partnerEntity).join([
innerJoin( innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedById)),
_db.userEntity, ])..where(_db.partnerEntity.sharedWithId.equals(userId));
_db.userEntity.id.equalsExp(_db.partnerEntity.sharedById),
),
])
..where(
_db.partnerEntity.sharedWithId.equals(userId),
);
return query.map((row) { return query.map((row) {
final user = row.readTable(_db.userEntity); final user = row.readTable(_db.userEntity);
final partner = row.readTable(_db.partnerEntity); final partner = row.readTable(_db.partnerEntity);
return PartnerUserDto( return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline);
id: user.id,
email: user.email,
name: user.name,
inTimeline: partner.inTimeline,
);
}).get(); }).get();
} }
@ -35,60 +24,33 @@ class DriftPartnerRepository extends DriftDatabaseRepository {
final query = _db.select(_db.userEntity)..where((row) => row.id.equals(currentUserId).not()); final query = _db.select(_db.userEntity)..where((row) => row.id.equals(currentUserId).not());
return query.map((user) { return query.map((user) {
return PartnerUserDto( return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: false);
id: user.id,
email: user.email,
name: user.name,
inTimeline: false,
);
}).get(); }).get();
} }
// Get users who are sharing their photos WITH the current user // Get users who are sharing their photos WITH the current user
Future<List<PartnerUserDto>> getSharedWith(String partnerId) { Future<List<PartnerUserDto>> getSharedWith(String partnerId) {
final query = _db.select(_db.partnerEntity).join([ final query = _db.select(_db.partnerEntity).join([
innerJoin( innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedById)),
_db.userEntity, ])..where(_db.partnerEntity.sharedWithId.equals(partnerId));
_db.userEntity.id.equalsExp(_db.partnerEntity.sharedById),
),
])
..where(
_db.partnerEntity.sharedWithId.equals(partnerId),
);
return query.map((row) { return query.map((row) {
final user = row.readTable(_db.userEntity); final user = row.readTable(_db.userEntity);
final partner = row.readTable(_db.partnerEntity); final partner = row.readTable(_db.partnerEntity);
return PartnerUserDto( return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline);
id: user.id,
email: user.email,
name: user.name,
inTimeline: partner.inTimeline,
);
}).get(); }).get();
} }
// Get users who the current user is sharing their photos TO // Get users who the current user is sharing their photos TO
Future<List<PartnerUserDto>> getSharedBy(String userId) { Future<List<PartnerUserDto>> getSharedBy(String userId) {
final query = _db.select(_db.partnerEntity).join([ final query = _db.select(_db.partnerEntity).join([
innerJoin( innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedWithId)),
_db.userEntity, ])..where(_db.partnerEntity.sharedById.equals(userId));
_db.userEntity.id.equalsExp(_db.partnerEntity.sharedWithId),
),
])
..where(
_db.partnerEntity.sharedById.equals(userId),
);
return query.map((row) { return query.map((row) {
final user = row.readTable(_db.userEntity); final user = row.readTable(_db.userEntity);
final partner = row.readTable(_db.partnerEntity); final partner = row.readTable(_db.partnerEntity);
return PartnerUserDto( return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline);
id: user.id,
email: user.email,
name: user.name,
inTimeline: partner.inTimeline,
);
}).get(); }).get();
} }
@ -108,35 +70,24 @@ class DriftPartnerRepository extends DriftDatabaseRepository {
Future<PartnerUserDto?> getPartner(String partnerId, String userId) { Future<PartnerUserDto?> getPartner(String partnerId, String userId) {
final query = _db.select(_db.partnerEntity).join([ final query = _db.select(_db.partnerEntity).join([
innerJoin( innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedById)),
_db.userEntity, ])..where(_db.partnerEntity.sharedById.equals(partnerId) & _db.partnerEntity.sharedWithId.equals(userId));
_db.userEntity.id.equalsExp(_db.partnerEntity.sharedById),
),
])
..where(
_db.partnerEntity.sharedById.equals(partnerId) & _db.partnerEntity.sharedWithId.equals(userId),
);
return query.map((row) { return query.map((row) {
final user = row.readTable(_db.userEntity); final user = row.readTable(_db.userEntity);
final partner = row.readTable(_db.partnerEntity); final partner = row.readTable(_db.partnerEntity);
return PartnerUserDto( return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline);
id: user.id,
email: user.email,
name: user.name,
inTimeline: partner.inTimeline,
);
}).getSingleOrNull(); }).getSingleOrNull();
} }
Future<bool> toggleShowInTimeline(PartnerUserDto partner, String userId) { Future<bool> toggleShowInTimeline(PartnerUserDto partner, String userId) {
return _db.partnerEntity.update().replace( return _db.partnerEntity.update().replace(
PartnerEntityCompanion( PartnerEntityCompanion(
sharedById: Value(partner.id), sharedById: Value(partner.id),
sharedWithId: Value(userId), sharedWithId: Value(userId),
inTimeline: Value(!partner.inTimeline), inTimeline: Value(!partner.inTimeline),
), ),
); );
} }
Future<int> create(String partnerId, String userId) { Future<int> create(String partnerId, String userId) {
@ -150,8 +101,6 @@ class DriftPartnerRepository extends DriftDatabaseRepository {
} }
Future<void> delete(String partnerId, String userId) { Future<void> delete(String partnerId, String userId) {
return _db.partnerEntity.deleteWhere( return _db.partnerEntity.deleteWhere((t) => t.sharedById.equals(userId) & t.sharedWithId.equals(partnerId));
(t) => t.sharedById.equals(userId) & t.sharedWithId.equals(partnerId),
);
} }
} }

View file

@ -16,9 +16,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
final Drift _db; final Drift _db;
const DriftRemoteAlbumRepository(this._db) : super(_db); const DriftRemoteAlbumRepository(this._db) : super(_db);
Future<List<RemoteAlbum>> getAll({ Future<List<RemoteAlbum>> getAll({Set<SortRemoteAlbumsBy> sortBy = const {SortRemoteAlbumsBy.updatedAt}}) {
Set<SortRemoteAlbumsBy> sortBy = const {SortRemoteAlbumsBy.updatedAt},
}) {
final assetCount = _db.remoteAlbumAssetEntity.assetId.count(); final assetCount = _db.remoteAlbumAssetEntity.assetId.count();
final query = _db.remoteAlbumEntity.select().join([ final query = _db.remoteAlbumEntity.select().join([
@ -32,11 +30,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false, useColumns: false,
), ),
leftOuterJoin( leftOuterJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false),
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false,
),
]); ]);
query query
..where(_db.remoteAssetEntity.deletedAt.isNull()) ..where(_db.remoteAssetEntity.deletedAt.isNull())
@ -47,22 +41,19 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
if (sortBy.isNotEmpty) { if (sortBy.isNotEmpty) {
final orderings = <OrderingTerm>[]; final orderings = <OrderingTerm>[];
for (final sort in sortBy) { for (final sort in sortBy) {
orderings.add( orderings.add(switch (sort) {
switch (sort) { SortRemoteAlbumsBy.id => OrderingTerm.asc(_db.remoteAlbumEntity.id),
SortRemoteAlbumsBy.id => OrderingTerm.asc(_db.remoteAlbumEntity.id), SortRemoteAlbumsBy.updatedAt => OrderingTerm.desc(_db.remoteAlbumEntity.updatedAt),
SortRemoteAlbumsBy.updatedAt => OrderingTerm.desc(_db.remoteAlbumEntity.updatedAt), });
},
);
} }
query.orderBy(orderings); query.orderBy(orderings);
} }
return query return query
.map( .map(
(row) => row.readTable(_db.remoteAlbumEntity).toDto( (row) => row
assetCount: row.read(assetCount) ?? 0, .readTable(_db.remoteAlbumEntity)
ownerName: row.read(_db.userEntity.name)!, .toDto(assetCount: row.read(assetCount) ?? 0, ownerName: row.read(_db.userEntity.name)!),
),
) )
.get(); .get();
} }
@ -70,42 +61,39 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
Future<RemoteAlbum?> get(String albumId) { Future<RemoteAlbum?> get(String albumId) {
final assetCount = _db.remoteAlbumAssetEntity.assetId.count(); final assetCount = _db.remoteAlbumAssetEntity.assetId.count();
final query = _db.remoteAlbumEntity.select().join([ final query =
leftOuterJoin( _db.remoteAlbumEntity.select().join([
_db.remoteAlbumAssetEntity, leftOuterJoin(
_db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), _db.remoteAlbumAssetEntity,
useColumns: false, _db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
), useColumns: false,
leftOuterJoin( ),
_db.remoteAssetEntity, leftOuterJoin(
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), _db.remoteAssetEntity,
useColumns: false, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
), useColumns: false,
leftOuterJoin( ),
_db.userEntity, leftOuterJoin(
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), _db.userEntity,
useColumns: false, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
), useColumns: false,
]) ),
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull()) ])
..addColumns([assetCount]) ..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
..addColumns([_db.userEntity.name]) ..addColumns([assetCount])
..groupBy([_db.remoteAlbumEntity.id]); ..addColumns([_db.userEntity.name])
..groupBy([_db.remoteAlbumEntity.id]);
return query return query
.map( .map(
(row) => row.readTable(_db.remoteAlbumEntity).toDto( (row) => row
assetCount: row.read(assetCount) ?? 0, .readTable(_db.remoteAlbumEntity)
ownerName: row.read(_db.userEntity.name)!, .toDto(assetCount: row.read(assetCount) ?? 0, ownerName: row.read(_db.userEntity.name)!),
),
) )
.getSingleOrNull(); .getSingleOrNull();
} }
Future<void> create( Future<void> create(RemoteAlbum album, List<String> assetIds) async {
RemoteAlbum album,
List<String> assetIds,
) async {
await _db.transaction(() async { await _db.transaction(() async {
final entity = RemoteAlbumEntityCompanion( final entity = RemoteAlbumEntityCompanion(
id: Value(album.id), id: Value(album.id),
@ -123,17 +111,11 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
if (assetIds.isNotEmpty) { if (assetIds.isNotEmpty) {
final albumAssets = assetIds.map( final albumAssets = assetIds.map(
(assetId) => RemoteAlbumAssetEntityCompanion( (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)),
albumId: Value(album.id),
assetId: Value(assetId),
),
); );
await _db.batch((batch) { await _db.batch((batch) {
batch.insertAll( batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets);
_db.remoteAlbumAssetEntity,
albumAssets,
);
}); });
} }
}); });
@ -141,38 +123,30 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
Future<void> update(RemoteAlbum album) async { Future<void> update(RemoteAlbum album) async {
await _db.remoteAlbumEntity.update().replace( await _db.remoteAlbumEntity.update().replace(
RemoteAlbumEntityCompanion( RemoteAlbumEntityCompanion(
id: Value(album.id), id: Value(album.id),
name: Value(album.name), name: Value(album.name),
ownerId: Value(album.ownerId), ownerId: Value(album.ownerId),
createdAt: Value(album.createdAt), createdAt: Value(album.createdAt),
updatedAt: Value(album.updatedAt), updatedAt: Value(album.updatedAt),
description: Value(album.description), description: Value(album.description),
thumbnailAssetId: Value(album.thumbnailAssetId), thumbnailAssetId: Value(album.thumbnailAssetId),
isActivityEnabled: Value(album.isActivityEnabled), isActivityEnabled: Value(album.isActivityEnabled),
order: Value(album.order), order: Value(album.order),
), ),
); );
} }
Future<int> removeAssets(String albumId, List<String> assetIds) { Future<int> removeAssets(String albumId, List<String> assetIds) {
return _db.remoteAlbumAssetEntity.deleteWhere( return _db.remoteAlbumAssetEntity.deleteWhere((tbl) => tbl.albumId.equals(albumId) & tbl.assetId.isIn(assetIds));
(tbl) => tbl.albumId.equals(albumId) & tbl.assetId.isIn(assetIds),
);
} }
FutureOr<(DateTime, DateTime)> getDateRange(String albumId) { FutureOr<(DateTime, DateTime)> getDateRange(String albumId) {
final query = _db.remoteAlbumAssetEntity.selectOnly() final query = _db.remoteAlbumAssetEntity.selectOnly()
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId)) ..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId))
..addColumns([ ..addColumns([_db.remoteAssetEntity.createdAt.min(), _db.remoteAssetEntity.createdAt.max()])
_db.remoteAssetEntity.createdAt.min(),
_db.remoteAssetEntity.createdAt.max(),
])
..join([ ..join([
innerJoin( innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
),
]); ]);
return query.map((row) { return query.map((row) {
@ -183,8 +157,9 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
} }
Future<List<UserDto>> getSharedUsers(String albumId) async { Future<List<UserDto>> getSharedUsers(String albumId) async {
final albumUserRows = final albumUserRows = await (_db.select(
await (_db.select(_db.remoteAlbumUserEntity)..where((row) => row.albumId.equals(albumId))).get(); _db.remoteAlbumUserEntity,
)..where((row) => row.albumId.equals(albumId))).get();
if (albumUserRows.isEmpty) { if (albumUserRows.isEmpty) {
return []; return [];
@ -214,29 +189,19 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
Future<List<RemoteAsset>> getAssets(String albumId) { Future<List<RemoteAsset>> getAssets(String albumId) {
final query = _db.remoteAlbumAssetEntity.select().join([ final query = _db.remoteAlbumAssetEntity.select().join([
innerJoin( innerJoin(_db.remoteAssetEntity, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId)),
_db.remoteAssetEntity, ])..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId));
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
),
])
..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId));
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get();
} }
Future<int> addAssets(String albumId, List<String> assetIds) async { Future<int> addAssets(String albumId, List<String> assetIds) async {
final albumAssets = assetIds.map( final albumAssets = assetIds.map(
(assetId) => RemoteAlbumAssetEntityCompanion( (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(albumId), assetId: Value(assetId)),
albumId: Value(albumId),
assetId: Value(assetId),
),
); );
await _db.batch((batch) { await _db.batch((batch) {
batch.insertAll( batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets);
_db.remoteAlbumAssetEntity,
albumAssets,
);
}); });
return assetIds.length; return assetIds.length;
@ -252,47 +217,41 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
); );
return _db.batch((batch) { return _db.batch((batch) {
batch.insertAll( batch.insertAll(_db.remoteAlbumUserEntity, albumUsers);
_db.remoteAlbumUserEntity,
albumUsers,
);
}); });
} }
Future<void> deleteAlbum(String albumId) async { Future<void> deleteAlbum(String albumId) async {
return _db.transaction(() async { return _db.transaction(() async {
await _db.remoteAlbumEntity.deleteWhere( await _db.remoteAlbumEntity.deleteWhere((table) => table.id.equals(albumId));
(table) => table.id.equals(albumId),
);
}); });
} }
Stream<RemoteAlbum?> watchAlbum(String albumId) { Stream<RemoteAlbum?> watchAlbum(String albumId) {
final query = _db.remoteAlbumEntity.select().join([ final query =
leftOuterJoin( _db.remoteAlbumEntity.select().join([
_db.remoteAlbumAssetEntity, leftOuterJoin(
_db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), _db.remoteAlbumAssetEntity,
useColumns: false, _db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
), useColumns: false,
leftOuterJoin( ),
_db.remoteAssetEntity, leftOuterJoin(
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), _db.remoteAssetEntity,
useColumns: false, _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
), useColumns: false,
leftOuterJoin( ),
_db.userEntity, leftOuterJoin(
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), _db.userEntity,
useColumns: false, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
), useColumns: false,
]) ),
..where(_db.remoteAlbumEntity.id.equals(albumId)) ])
..addColumns([_db.userEntity.name]) ..where(_db.remoteAlbumEntity.id.equals(albumId))
..groupBy([_db.remoteAlbumEntity.id]); ..addColumns([_db.userEntity.name])
..groupBy([_db.remoteAlbumEntity.id]);
return query.map((row) { return query.map((row) {
final album = row.readTable(_db.remoteAlbumEntity).toDto( final album = row.readTable(_db.remoteAlbumEntity).toDto(ownerName: row.read(_db.userEntity.name)!);
ownerName: row.read(_db.userEntity.name)!,
);
return album; return album;
}).watchSingleOrNull(); }).watchSingleOrNull();
} }

View file

@ -30,17 +30,16 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
} }
SingleOrNullSelectable<RemoteAsset?> _assetSelectable(String id) { SingleOrNullSelectable<RemoteAsset?> _assetSelectable(String id) {
final query = _db.remoteAssetEntity.select().addColumns([ final query =
_db.localAssetEntity.id, _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([
]).join([ leftOuterJoin(
leftOuterJoin( _db.localAssetEntity,
_db.localAssetEntity, _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), useColumns: false,
useColumns: false, ),
), ])
]) ..where(_db.remoteAssetEntity.id.equals(id))
..where(_db.remoteAssetEntity.id.equals(id)) ..limit(1);
..limit(1);
return query.map((row) { return query.map((row) {
final asset = row.readTable(_db.remoteAssetEntity).toDto(); final asset = row.readTable(_db.remoteAssetEntity).toDto();
@ -57,17 +56,16 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
} }
Stream<RemoteAsset?> watchAsset(String id) { Stream<RemoteAsset?> watchAsset(String id) {
final query = _db.remoteAssetEntity.select().addColumns([ final query =
_db.localAssetEntity.id, _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([
]).join([ leftOuterJoin(
leftOuterJoin( _db.localAssetEntity,
_db.localAssetEntity, _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), useColumns: false,
useColumns: false, ),
), ])
]) ..where(_db.remoteAssetEntity.id.equals(id))
..where(_db.remoteAssetEntity.id.equals(id)) ..limit(1);
..limit(1);
return query.map((row) { return query.map((row) {
final asset = row.readTable(_db.remoteAssetEntity).toDto(); final asset = row.readTable(_db.remoteAssetEntity).toDto();
@ -81,9 +79,7 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
} }
final query = _db.remoteAssetEntity.select() final query = _db.remoteAssetEntity.select()
..where( ..where((row) => row.stackId.equals(asset.stackId!) & row.id.equals(asset.id).not())
(row) => row.stackId.equals(asset.stackId!) & row.id.equals(asset.id).not(),
)
..orderBy([(row) => OrderingTerm.desc(row.createdAt)]); ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]);
return query.map((row) => row.toDto()).get(); return query.map((row) => row.toDto()).get();
@ -102,24 +98,22 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
"asset", "asset",
); );
final query = asset.selectOnly().join([ final query =
innerJoin( asset.selectOnly().join([
_db.remoteExifEntity, innerJoin(
_db.remoteExifEntity.assetId.equalsExp(asset.ref(_db.remoteAssetEntity.id)), _db.remoteExifEntity,
useColumns: false, _db.remoteExifEntity.assetId.equalsExp(asset.ref(_db.remoteAssetEntity.id)),
), useColumns: false,
]) ),
..addColumns([ ])
_db.remoteExifEntity.city, ..addColumns([_db.remoteExifEntity.city, _db.remoteExifEntity.assetId])
_db.remoteExifEntity.assetId, ..where(
]) _db.remoteExifEntity.city.isNotNull() &
..where( asset.ref(_db.remoteAssetEntity.deletedAt).isNull() &
_db.remoteExifEntity.city.isNotNull() & asset.ref(_db.remoteAssetEntity.visibility).equals(AssetVisibility.timeline.index),
asset.ref(_db.remoteAssetEntity.deletedAt).isNull() & )
asset.ref(_db.remoteAssetEntity.visibility).equals(AssetVisibility.timeline.index), ..groupBy([_db.remoteExifEntity.city])
) ..orderBy([OrderingTerm.asc(_db.remoteExifEntity.city)]);
..groupBy([_db.remoteExifEntity.city])
..orderBy([OrderingTerm.asc(_db.remoteExifEntity.city)]);
return query.map((row) { return query.map((row) {
final assetId = row.read(_db.remoteExifEntity.assetId); final assetId = row.read(_db.remoteExifEntity.assetId);
@ -185,10 +179,7 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
for (final id in ids) { for (final id in ids) {
batch.update( batch.update(
_db.remoteExifEntity, _db.remoteExifEntity,
RemoteExifEntityCompanion( RemoteExifEntityCompanion(latitude: Value(location.latitude), longitude: Value(location.longitude)),
latitude: Value(location.latitude),
longitude: Value(location.longitude),
),
where: (e) => e.assetId.equals(id), where: (e) => e.assetId.equals(id),
); );
} }
@ -205,23 +196,14 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds)); await _db.stackEntity.deleteWhere((row) => row.id.isIn(stackIds));
await _db.batch((batch) { await _db.batch((batch) {
final companion = StackEntityCompanion( final companion = StackEntityCompanion(ownerId: Value(userId), primaryAssetId: Value(stack.primaryAssetId));
ownerId: Value(userId),
primaryAssetId: Value(stack.primaryAssetId),
);
batch.insert( batch.insert(_db.stackEntity, companion.copyWith(id: Value(stack.id)), onConflict: DoUpdate((_) => companion));
_db.stackEntity,
companion.copyWith(id: Value(stack.id)),
onConflict: DoUpdate((_) => companion),
);
for (final assetId in stack.assetIds) { for (final assetId in stack.assetIds) {
batch.update( batch.update(
_db.remoteAssetEntity, _db.remoteAssetEntity,
RemoteAssetEntityCompanion( RemoteAssetEntityCompanion(stackId: Value(stack.id)),
stackId: Value(stack.id),
),
where: (e) => e.id.equals(assetId), where: (e) => e.id.equals(assetId),
); );
} }

View file

@ -66,12 +66,5 @@ class SearchApiRepository extends ApiRepository {
String? state, String? state,
String? make, String? make,
String? model, String? model,
}) => }) => _api.getSearchSuggestions(type, country: country, state: state, make: make, model: model);
_api.getSearchSuggestions(
type,
country: country,
state: state,
make: make,
model: model,
);
} }

View file

@ -18,12 +18,6 @@ class DriftStackRepository extends DriftDatabaseRepository {
extension on StackEntityData { extension on StackEntityData {
Stack toDto() { Stack toDto() {
return Stack( return Stack(id: id, createdAt: createdAt, updatedAt: updatedAt, ownerId: ownerId, primaryAssetId: primaryAssetId);
id: id,
createdAt: createdAt,
updatedAt: updatedAt,
ownerId: ownerId,
primaryAssetId: primaryAssetId,
);
} }
} }

View file

@ -23,11 +23,7 @@ class IsarStoreRepository extends IsarDatabaseRepository {
.filter() .filter()
.anyOf(validStoreKeys, (query, id) => query.idEqualTo(id)) .anyOf(validStoreKeys, (query, id) => query.idEqualTo(id))
.watch(fireImmediately: true) .watch(fireImmediately: true)
.asyncExpand( .asyncExpand((entities) => Stream.fromFutures(entities.map((e) async => _toUpdateEvent(e))));
(entities) => Stream.fromFutures(
entities.map((e) async => _toUpdateEvent(e)),
),
);
} }
Future<void> delete<T>(StoreKey<T> key) async { Future<void> delete<T>(StoreKey<T> key) async {
@ -68,14 +64,17 @@ class IsarStoreRepository extends IsarDatabaseRepository {
return StoreDto(key, value); return StoreDto(key, value);
} }
Future<T?> _toValue<T>(StoreKey<T> key, StoreValue entity) async => switch (key.type) { Future<T?> _toValue<T>(StoreKey<T> key, StoreValue entity) async =>
const (int) => entity.intValue, switch (key.type) {
const (String) => entity.strValue, const (int) => entity.intValue,
const (bool) => entity.intValue == 1, const (String) => entity.strValue,
const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), const (bool) => entity.intValue == 1,
const (UserDto) => entity.strValue == null ? null : await IsarUserRepository(_db).getByUserId(entity.strValue!), const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!),
_ => null, const (UserDto) =>
} as T?; entity.strValue == null ? null : await IsarUserRepository(_db).getByUserId(entity.strValue!),
_ => null,
}
as T?;
Future<StoreValue> _fromValue<T>(StoreKey<T> key, T value) async { Future<StoreValue> _fromValue<T>(StoreKey<T> key, T value) async {
final (int? intValue, String? strValue) = switch (key.type) { final (int? intValue, String? strValue) = switch (key.type) {
@ -83,13 +82,8 @@ class IsarStoreRepository extends IsarDatabaseRepository {
const (String) => (null, value as String), const (String) => (null, value as String),
const (bool) => ((value as bool) ? 1 : 0, null), const (bool) => ((value as bool) ? 1 : 0, null),
const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null),
const (UserDto) => ( const (UserDto) => (null, (await IsarUserRepository(_db).update(value as UserDto)).id),
null, _ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"),
(await IsarUserRepository(_db).update(value as UserDto)).id,
),
_ => throw UnsupportedError(
"Unsupported primitive type: ${key.type} for key: ${key.name}",
),
}; };
return StoreValue(key.id, intValue: intValue, strValue: strValue); return StoreValue(key.id, intValue: intValue, strValue: strValue);
} }

View file

@ -27,10 +27,7 @@ class SyncApiRepository {
final client = httpClient ?? http.Client(); final client = httpClient ?? http.Client();
final endpoint = "${_api.apiClient.basePath}/sync/stream"; final endpoint = "${_api.apiClient.basePath}/sync/stream";
final headers = { final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'};
'Content-Type': 'application/json',
'Accept': 'application/jsonlines+json',
};
final headerParams = <String, String>{}; final headerParams = <String, String>{};
await _api.applyToParams([], headerParams); await _api.applyToParams([], headerParams);
@ -78,10 +75,7 @@ class SyncApiRepository {
if (response.statusCode != 200) { if (response.statusCode != 200) {
final errorBody = await response.stream.bytesToString(); final errorBody = await response.stream.bytesToString();
throw ApiException( throw ApiException(response.statusCode, 'Failed to get sync stream: $errorBody');
response.statusCode,
'Failed to get sync stream: $errorBody',
);
} }
await for (final chunk in response.stream.transform(utf8.decoder)) { await for (final chunk in response.stream.transform(utf8.decoder)) {

View file

@ -42,16 +42,9 @@ class SyncStreamRepository extends DriftDatabaseRepository {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final user in data) { for (final user in data) {
final companion = UserEntityCompanion( final companion = UserEntityCompanion(name: Value(user.name), email: Value(user.email));
name: Value(user.name),
email: Value(user.email),
);
batch.insert( batch.insert(_db.userEntity, companion.copyWith(id: Value(user.id)), onConflict: DoUpdate((_) => companion));
_db.userEntity,
companion.copyWith(id: Value(user.id)),
onConflict: DoUpdate((_) => companion),
);
} }
}); });
} catch (error, stack) { } catch (error, stack) {
@ -66,10 +59,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
for (final partner in data) { for (final partner in data) {
batch.delete( batch.delete(
_db.partnerEntity, _db.partnerEntity,
PartnerEntityCompanion( PartnerEntityCompanion(sharedById: Value(partner.sharedById), sharedWithId: Value(partner.sharedWithId)),
sharedById: Value(partner.sharedById),
sharedWithId: Value(partner.sharedWithId),
),
); );
} }
}); });
@ -87,10 +77,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
batch.insert( batch.insert(
_db.partnerEntity, _db.partnerEntity,
companion.copyWith( companion.copyWith(sharedById: Value(partner.sharedById), sharedWithId: Value(partner.sharedWithId)),
sharedById: Value(partner.sharedById),
sharedWithId: Value(partner.sharedWithId),
),
onConflict: DoUpdate((_) => companion), onConflict: DoUpdate((_) => companion),
); );
} }
@ -101,24 +88,16 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> deleteAssetsV1( Future<void> deleteAssetsV1(Iterable<SyncAssetDeleteV1> data, {String debugLabel = 'user'}) async {
Iterable<SyncAssetDeleteV1> data, {
String debugLabel = 'user',
}) async {
try { try {
await _db.remoteAssetEntity.deleteWhere( await _db.remoteAssetEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.assetId)));
(row) => row.id.isIn(data.map((e) => e.assetId)),
);
} catch (error, stack) { } catch (error, stack) {
_logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stack); _logger.severe('Error: deleteAssetsV1 - $debugLabel', error, stack);
rethrow; rethrow;
} }
} }
Future<void> updateAssetsV1( Future<void> updateAssetsV1(Iterable<SyncAssetV1> data, {String debugLabel = 'user'}) async {
Iterable<SyncAssetV1> data, {
String debugLabel = 'user',
}) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final asset in data) { for (final asset in data) {
@ -152,10 +131,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> updateAssetsExifV1( Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
Iterable<SyncAssetExifV1> data, {
String debugLabel = 'user',
}) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final exif in data) { for (final exif in data) {
@ -191,20 +167,14 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
}); });
} catch (error, stack) { } catch (error, stack) {
_logger.severe( _logger.severe('Error: updateAssetsExifV1 - $debugLabel', error, stack);
'Error: updateAssetsExifV1 - $debugLabel',
error,
stack,
);
rethrow; rethrow;
} }
} }
Future<void> deleteAlbumsV1(Iterable<SyncAlbumDeleteV1> data) async { Future<void> deleteAlbumsV1(Iterable<SyncAlbumDeleteV1> data) async {
try { try {
await _db.remoteAlbumEntity.deleteWhere( await _db.remoteAlbumEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.albumId)));
(row) => row.id.isIn(data.map((e) => e.albumId)),
);
} catch (error, stack) { } catch (error, stack) {
_logger.severe('Error: deleteAlbumsV1', error, stack); _logger.severe('Error: deleteAlbumsV1', error, stack);
rethrow; rethrow;
@ -245,10 +215,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
for (final album in data) { for (final album in data) {
batch.delete( batch.delete(
_db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity,
RemoteAlbumUserEntityCompanion( RemoteAlbumUserEntityCompanion(albumId: Value(album.albumId), userId: Value(album.userId)),
albumId: Value(album.albumId),
userId: Value(album.userId),
),
); );
} }
}); });
@ -258,49 +225,32 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> updateAlbumUsersV1( Future<void> updateAlbumUsersV1(Iterable<SyncAlbumUserV1> data, {String debugLabel = 'user'}) async {
Iterable<SyncAlbumUserV1> data, {
String debugLabel = 'user',
}) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final album in data) { for (final album in data) {
final companion = RemoteAlbumUserEntityCompanion( final companion = RemoteAlbumUserEntityCompanion(role: Value(album.role.toAlbumUserRole()));
role: Value(album.role.toAlbumUserRole()),
);
batch.insert( batch.insert(
_db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity,
companion.copyWith( companion.copyWith(albumId: Value(album.albumId), userId: Value(album.userId)),
albumId: Value(album.albumId),
userId: Value(album.userId),
),
onConflict: DoUpdate((_) => companion), onConflict: DoUpdate((_) => companion),
); );
} }
}); });
} catch (error, stack) { } catch (error, stack) {
_logger.severe( _logger.severe('Error: updateAlbumUsersV1 - $debugLabel', error, stack);
'Error: updateAlbumUsersV1 - $debugLabel',
error,
stack,
);
rethrow; rethrow;
} }
} }
Future<void> deleteAlbumToAssetsV1( Future<void> deleteAlbumToAssetsV1(Iterable<SyncAlbumToAssetDeleteV1> data) async {
Iterable<SyncAlbumToAssetDeleteV1> data,
) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final album in data) { for (final album in data) {
batch.delete( batch.delete(
_db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity,
RemoteAlbumAssetEntityCompanion( RemoteAlbumAssetEntityCompanion(albumId: Value(album.albumId), assetId: Value(album.assetId)),
albumId: Value(album.albumId),
assetId: Value(album.assetId),
),
); );
} }
}); });
@ -310,10 +260,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> updateAlbumToAssetsV1( Future<void> updateAlbumToAssetsV1(Iterable<SyncAlbumToAssetV1> data, {String debugLabel = 'user'}) async {
Iterable<SyncAlbumToAssetV1> data, {
String debugLabel = 'user',
}) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final album in data) { for (final album in data) {
@ -322,19 +269,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
assetId: Value(album.assetId), assetId: Value(album.assetId),
); );
batch.insert( batch.insert(_db.remoteAlbumAssetEntity, companion, onConflict: DoNothing());
_db.remoteAlbumAssetEntity,
companion,
onConflict: DoNothing(),
);
} }
}); });
} catch (error, stack) { } catch (error, stack) {
_logger.severe( _logger.severe('Error: updateAlbumToAssetsV1 - $debugLabel', error, stack);
'Error: updateAlbumToAssetsV1 - $debugLabel',
error,
stack,
);
rethrow; rethrow;
} }
} }
@ -371,9 +310,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
Future<void> deleteMemoriesV1(Iterable<SyncMemoryDeleteV1> data) async { Future<void> deleteMemoriesV1(Iterable<SyncMemoryDeleteV1> data) async {
try { try {
await _db.memoryEntity.deleteWhere( await _db.memoryEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.memoryId)));
(row) => row.id.isIn(data.map((e) => e.memoryId)),
);
} catch (error, stack) { } catch (error, stack) {
_logger.severe('Error: deleteMemoriesV1', error, stack); _logger.severe('Error: deleteMemoriesV1', error, stack);
rethrow; rethrow;
@ -384,16 +321,9 @@ class SyncStreamRepository extends DriftDatabaseRepository {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final asset in data) { for (final asset in data) {
final companion = MemoryAssetEntityCompanion( final companion = MemoryAssetEntityCompanion(memoryId: Value(asset.memoryId), assetId: Value(asset.assetId));
memoryId: Value(asset.memoryId),
assetId: Value(asset.assetId),
);
batch.insert( batch.insert(_db.memoryAssetEntity, companion, onConflict: DoNothing());
_db.memoryAssetEntity,
companion,
onConflict: DoNothing(),
);
} }
}); });
} catch (error, stack) { } catch (error, stack) {
@ -402,18 +332,13 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> deleteMemoryAssetsV1( Future<void> deleteMemoryAssetsV1(Iterable<SyncMemoryAssetDeleteV1> data) async {
Iterable<SyncMemoryAssetDeleteV1> data,
) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final asset in data) { for (final asset in data) {
batch.delete( batch.delete(
_db.memoryAssetEntity, _db.memoryAssetEntity,
MemoryAssetEntityCompanion( MemoryAssetEntityCompanion(memoryId: Value(asset.memoryId), assetId: Value(asset.assetId)),
memoryId: Value(asset.memoryId),
assetId: Value(asset.assetId),
),
); );
} }
}); });
@ -423,10 +348,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> updateStacksV1( Future<void> updateStacksV1(Iterable<SyncStackV1> data, {String debugLabel = 'user'}) async {
Iterable<SyncStackV1> data, {
String debugLabel = 'user',
}) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final stack in data) { for (final stack in data) {
@ -450,36 +372,24 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> deleteStacksV1( Future<void> deleteStacksV1(Iterable<SyncStackDeleteV1> data, {String debugLabel = 'user'}) async {
Iterable<SyncStackDeleteV1> data, {
String debugLabel = 'user',
}) async {
try { try {
await _db.stackEntity.deleteWhere( await _db.stackEntity.deleteWhere((row) => row.id.isIn(data.map((e) => e.stackId)));
(row) => row.id.isIn(data.map((e) => e.stackId)),
);
} catch (error, stack) { } catch (error, stack) {
_logger.severe('Error: deleteStacksV1 - $debugLabel', error, stack); _logger.severe('Error: deleteStacksV1 - $debugLabel', error, stack);
rethrow; rethrow;
} }
} }
Future<void> updateUserMetadatasV1( Future<void> updateUserMetadatasV1(Iterable<SyncUserMetadataV1> data) async {
Iterable<SyncUserMetadataV1> data,
) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final userMetadata in data) { for (final userMetadata in data) {
final companion = UserMetadataEntityCompanion( final companion = UserMetadataEntityCompanion(value: Value(userMetadata.value as Map<String, Object?>));
value: Value(userMetadata.value as Map<String, Object?>),
);
batch.insert( batch.insert(
_db.userMetadataEntity, _db.userMetadataEntity,
companion.copyWith( companion.copyWith(userId: Value(userMetadata.userId), key: Value(userMetadata.key.toUserMetadataKey())),
userId: Value(userMetadata.userId),
key: Value(userMetadata.key.toUserMetadataKey()),
),
onConflict: DoUpdate((_) => companion), onConflict: DoUpdate((_) => companion),
); );
} }
@ -490,9 +400,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> deleteUserMetadatasV1( Future<void> deleteUserMetadatasV1(Iterable<SyncUserMetadataDeleteV1> data) async {
Iterable<SyncUserMetadataDeleteV1> data,
) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final userMetadata in data) { for (final userMetadata in data) {
@ -540,16 +448,11 @@ class SyncStreamRepository extends DriftDatabaseRepository {
} }
} }
Future<void> deletePeopleV1( Future<void> deletePeopleV1(Iterable<SyncPersonDeleteV1> data) async {
Iterable<SyncPersonDeleteV1> data,
) async {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final person in data) { for (final person in data) {
batch.deleteWhere( batch.deleteWhere(_db.personEntity, (row) => row.id.equals(person.personId));
_db.personEntity,
(row) => row.id.equals(person.personId),
);
} }
}); });
} catch (error, stack) { } catch (error, stack) {
@ -591,10 +494,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
try { try {
await _db.batch((batch) { await _db.batch((batch) {
for (final assetFace in data) { for (final assetFace in data) {
batch.deleteWhere( batch.deleteWhere(_db.assetFaceEntity, (row) => row.id.equals(assetFace.assetFaceId));
_db.assetFaceEntity,
(row) => row.id.equals(assetFace.assetFaceId),
);
} }
}); });
} catch (error, stack) { } catch (error, stack) {
@ -606,54 +506,54 @@ class SyncStreamRepository extends DriftDatabaseRepository {
extension on AssetTypeEnum { extension on AssetTypeEnum {
AssetType toAssetType() => switch (this) { AssetType toAssetType() => switch (this) {
AssetTypeEnum.IMAGE => AssetType.image, AssetTypeEnum.IMAGE => AssetType.image,
AssetTypeEnum.VIDEO => AssetType.video, AssetTypeEnum.VIDEO => AssetType.video,
AssetTypeEnum.AUDIO => AssetType.audio, AssetTypeEnum.AUDIO => AssetType.audio,
AssetTypeEnum.OTHER => AssetType.other, AssetTypeEnum.OTHER => AssetType.other,
_ => throw Exception('Unknown AssetType value: $this'), _ => throw Exception('Unknown AssetType value: $this'),
}; };
} }
extension on AssetOrder { extension on AssetOrder {
AlbumAssetOrder toAlbumAssetOrder() => switch (this) { AlbumAssetOrder toAlbumAssetOrder() => switch (this) {
AssetOrder.asc => AlbumAssetOrder.asc, AssetOrder.asc => AlbumAssetOrder.asc,
AssetOrder.desc => AlbumAssetOrder.desc, AssetOrder.desc => AlbumAssetOrder.desc,
_ => throw Exception('Unknown AssetOrder value: $this'), _ => throw Exception('Unknown AssetOrder value: $this'),
}; };
} }
extension on MemoryType { extension on MemoryType {
MemoryTypeEnum toMemoryType() => switch (this) { MemoryTypeEnum toMemoryType() => switch (this) {
MemoryType.onThisDay => MemoryTypeEnum.onThisDay, MemoryType.onThisDay => MemoryTypeEnum.onThisDay,
_ => throw Exception('Unknown MemoryType value: $this'), _ => throw Exception('Unknown MemoryType value: $this'),
}; };
} }
extension on api.AlbumUserRole { extension on api.AlbumUserRole {
AlbumUserRole toAlbumUserRole() => switch (this) { AlbumUserRole toAlbumUserRole() => switch (this) {
api.AlbumUserRole.editor => AlbumUserRole.editor, api.AlbumUserRole.editor => AlbumUserRole.editor,
api.AlbumUserRole.viewer => AlbumUserRole.viewer, api.AlbumUserRole.viewer => AlbumUserRole.viewer,
_ => throw Exception('Unknown AlbumUserRole value: $this'), _ => throw Exception('Unknown AlbumUserRole value: $this'),
}; };
} }
extension on api.AssetVisibility { extension on api.AssetVisibility {
AssetVisibility toAssetVisibility() => switch (this) { AssetVisibility toAssetVisibility() => switch (this) {
api.AssetVisibility.timeline => AssetVisibility.timeline, api.AssetVisibility.timeline => AssetVisibility.timeline,
api.AssetVisibility.hidden => AssetVisibility.hidden, api.AssetVisibility.hidden => AssetVisibility.hidden,
api.AssetVisibility.archive => AssetVisibility.archive, api.AssetVisibility.archive => AssetVisibility.archive,
api.AssetVisibility.locked => AssetVisibility.locked, api.AssetVisibility.locked => AssetVisibility.locked,
_ => throw Exception('Unknown AssetVisibility value: $this'), _ => throw Exception('Unknown AssetVisibility value: $this'),
}; };
} }
extension on api.UserMetadataKey { extension on api.UserMetadataKey {
UserMetadataKey toUserMetadataKey() => switch (this) { UserMetadataKey toUserMetadataKey() => switch (this) {
api.UserMetadataKey.onboarding => UserMetadataKey.onboarding, api.UserMetadataKey.onboarding => UserMetadataKey.onboarding,
api.UserMetadataKey.preferences => UserMetadataKey.preferences, api.UserMetadataKey.preferences => UserMetadataKey.preferences,
api.UserMetadataKey.license => UserMetadataKey.license, api.UserMetadataKey.license => UserMetadataKey.license,
_ => throw Exception('Unknown UserMetadataKey value: $this'), _ => throw Exception('Unknown UserMetadataKey value: $this'),
}; };
} }
extension on String { extension on String {

View file

@ -21,9 +21,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
Stream<List<String>> watchTimelineUserIds(String userId) { Stream<List<String>> watchTimelineUserIds(String userId) {
final query = _db.partnerEntity.selectOnly() final query = _db.partnerEntity.selectOnly()
..addColumns([_db.partnerEntity.sharedById]) ..addColumns([_db.partnerEntity.sharedById])
..where( ..where(_db.partnerEntity.inTimeline.equals(true) & _db.partnerEntity.sharedWithId.equals(userId));
_db.partnerEntity.inTimeline.equals(true) & _db.partnerEntity.sharedWithId.equals(userId),
);
return query return query
.map((row) => row.read(_db.partnerEntity.sharedById)!) .map((row) => row.read(_db.partnerEntity.sharedById)!)
@ -33,25 +31,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
} }
TimelineQuery main(List<String> userIds, GroupAssetsBy groupBy) => ( TimelineQuery main(List<String> userIds, GroupAssetsBy groupBy) => (
bucketSource: () => _watchMainBucket( bucketSource: () => _watchMainBucket(userIds, groupBy: groupBy),
userIds, assetSource: (offset, count) => _getMainBucketAssets(userIds, offset: offset, count: count),
groupBy: groupBy, );
),
assetSource: (offset, count) => _getMainBucketAssets(
userIds,
offset: offset,
count: count,
),
);
Stream<List<Bucket>> _watchMainBucket( Stream<List<Bucket>> _watchMainBucket(List<String> userIds, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
List<String> userIds, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) { if (groupBy == GroupAssetsBy.none) {
throw UnsupportedError( throw UnsupportedError("GroupAssetsBy.none is not supported for watchMainBucket");
"GroupAssetsBy.none is not supported for watchMainBucket",
);
} }
return _db.mergedAssetDrift return _db.mergedAssetDrift
@ -64,11 +50,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.throttle(const Duration(seconds: 3), trailing: true); .throttle(const Duration(seconds: 3), trailing: true);
} }
Future<List<BaseAsset>> _getMainBucketAssets( Future<List<BaseAsset>> _getMainBucketAssets(List<String> userIds, {required int offset, required int count}) {
List<String> userIds, {
required int offset,
required int count,
}) {
return _db.mergedAssetDrift return _db.mergedAssetDrift
.mergedAsset(userIds, limit: (_) => Limit(count, offset)) .mergedAsset(userIds, limit: (_) => Limit(count, offset))
.map( .map(
@ -109,21 +91,11 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
} }
TimelineQuery localAlbum(String albumId, GroupAssetsBy groupBy) => ( TimelineQuery localAlbum(String albumId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchLocalAlbumBucket( bucketSource: () => _watchLocalAlbumBucket(albumId, groupBy: groupBy),
albumId, assetSource: (offset, count) => _getLocalAlbumBucketAssets(albumId, offset: offset, count: count),
groupBy: groupBy, );
),
assetSource: (offset, count) => _getLocalAlbumBucketAssets(
albumId,
offset: offset,
count: count,
),
);
Stream<List<Bucket>> _watchLocalAlbumBucket( Stream<List<Bucket>> _watchLocalAlbumBucket(String albumId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
String albumId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) { if (groupBy == GroupAssetsBy.none) {
return _db.localAlbumAssetEntity return _db.localAlbumAssetEntity
.count(where: (row) => row.albumId.equals(albumId)) .count(where: (row) => row.albumId.equals(albumId))
@ -134,22 +106,23 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
final assetCountExp = _db.localAssetEntity.id.count(); final assetCountExp = _db.localAssetEntity.id.count();
final dateExp = _db.localAssetEntity.createdAt.dateFmt(groupBy); final dateExp = _db.localAssetEntity.createdAt.dateFmt(groupBy);
final query = _db.localAssetEntity.selectOnly().join([ final query =
innerJoin( _db.localAssetEntity.selectOnly().join([
_db.localAlbumAssetEntity, innerJoin(
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), _db.localAlbumAssetEntity,
useColumns: false, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
), useColumns: false,
leftOuterJoin( ),
_db.remoteAssetEntity, leftOuterJoin(
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), _db.remoteAssetEntity,
useColumns: false, _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum),
), useColumns: false,
]) ),
..addColumns([assetCountExp, dateExp]) ])
..where(_db.localAlbumAssetEntity.albumId.equals(albumId)) ..addColumns([assetCountExp, dateExp])
..groupBy([dateExp]) ..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
..orderBy([OrderingTerm.desc(dateExp)]); ..groupBy([dateExp])
..orderBy([OrderingTerm.desc(dateExp)]);
return query.map((row) { return query.map((row) {
final timeline = row.read(dateExp)!.dateFmt(groupBy); final timeline = row.read(dateExp)!.dateFmt(groupBy);
@ -158,54 +131,37 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
}).watch(); }).watch();
} }
Future<List<BaseAsset>> _getLocalAlbumBucketAssets( Future<List<BaseAsset>> _getLocalAlbumBucketAssets(String albumId, {required int offset, required int count}) {
String albumId, { final query =
required int offset, _db.localAssetEntity.select().join([
required int count, innerJoin(
}) { _db.localAlbumAssetEntity,
final query = _db.localAssetEntity.select().join( _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id),
[ useColumns: false,
innerJoin( ),
_db.localAlbumAssetEntity, leftOuterJoin(
_db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), _db.remoteAssetEntity,
useColumns: false, _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum),
), useColumns: false,
leftOuterJoin( ),
_db.remoteAssetEntity, ])
_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), ..addColumns([_db.remoteAssetEntity.id])
useColumns: false, ..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
), ..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)])
], ..limit(count, offset: offset);
)
..addColumns([_db.remoteAssetEntity.id])
..where(_db.localAlbumAssetEntity.albumId.equals(albumId))
..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)])
..limit(count, offset: offset);
return query.map((row) { return query.map((row) {
final asset = row.readTable(_db.localAssetEntity).toDto(); final asset = row.readTable(_db.localAssetEntity).toDto();
return asset.copyWith( return asset.copyWith(remoteId: row.read(_db.remoteAssetEntity.id));
remoteId: row.read(_db.remoteAssetEntity.id),
);
}).get(); }).get();
} }
TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => ( TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchRemoteAlbumBucket( bucketSource: () => _watchRemoteAlbumBucket(albumId, groupBy: groupBy),
albumId, assetSource: (offset, count) => _getRemoteAlbumBucketAssets(albumId, offset: offset, count: count),
groupBy: groupBy, );
),
assetSource: (offset, count) => _getRemoteAlbumBucketAssets(
albumId,
offset: offset,
count: count,
),
);
Stream<List<Bucket>> _watchRemoteAlbumBucket( Stream<List<Bucket>> _watchRemoteAlbumBucket(String albumId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
String albumId, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) { if (groupBy == GroupAssetsBy.none) {
return _db.remoteAlbumAssetEntity return _db.remoteAlbumAssetEntity
.count(where: (row) => row.albumId.equals(albumId)) .count(where: (row) => row.albumId.equals(albumId))
@ -213,56 +169,53 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.watch() .watch()
.map((results) => results.isNotEmpty ? results.first : <Bucket>[]) .map((results) => results.isNotEmpty ? results.first : <Bucket>[])
.handleError((error) { .handleError((error) {
return []; return [];
}); });
} }
return (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId))).watch().switchMap((albums) { return (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId)))
if (albums.isEmpty) { .watch()
return Stream.value(<Bucket>[]); .switchMap((albums) {
} if (albums.isEmpty) {
return Stream.value(<Bucket>[]);
}
final album = albums.first; final album = albums.first;
final isAscending = album.order == AlbumAssetOrder.asc; final isAscending = album.order == AlbumAssetOrder.asc;
final assetCountExp = _db.remoteAssetEntity.id.count(); final assetCountExp = _db.remoteAssetEntity.id.count();
final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy); final dateExp = _db.remoteAssetEntity.createdAt.dateFmt(groupBy);
final query = _db.remoteAssetEntity.selectOnly() final query = _db.remoteAssetEntity.selectOnly()
..addColumns([assetCountExp, dateExp]) ..addColumns([assetCountExp, dateExp])
..join([ ..join([
innerJoin( innerJoin(
_db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id), _db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
useColumns: false, useColumns: false,
), ),
]) ])
..where( ..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId))
_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId), ..groupBy([dateExp]);
)
..groupBy([dateExp]);
if (isAscending) { if (isAscending) {
query.orderBy([OrderingTerm.asc(dateExp)]); query.orderBy([OrderingTerm.asc(dateExp)]);
} else { } else {
query.orderBy([OrderingTerm.desc(dateExp)]); query.orderBy([OrderingTerm.desc(dateExp)]);
} }
return query.map((row) { return query.map((row) {
final timeline = row.read(dateExp)!.dateFmt(groupBy); final timeline = row.read(dateExp)!.dateFmt(groupBy);
final assetCount = row.read(assetCountExp)!; final assetCount = row.read(assetCountExp)!;
return TimeBucket(date: timeline, assetCount: assetCount); return TimeBucket(date: timeline, assetCount: assetCount);
}).watch(); }).watch();
}).handleError((error) { })
// If there's an error (e.g., album was deleted), return empty buckets .handleError((error) {
return <Bucket>[]; // If there's an error (e.g., album was deleted), return empty buckets
}); return <Bucket>[];
});
} }
Future<List<BaseAsset>> _getRemoteAlbumBucketAssets( Future<List<BaseAsset>> _getRemoteAlbumBucketAssets(String albumId, {required int offset, required int count}) async {
String albumId, {
required int offset,
required int count,
}) async {
final albumData = await (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId))).getSingleOrNull(); final albumData = await (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId))).getSingleOrNull();
// If album doesn't exist (was deleted), return empty list // If album doesn't exist (was deleted), return empty list
@ -272,17 +225,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
final isAscending = albumData.order == AlbumAssetOrder.asc; final isAscending = albumData.order == AlbumAssetOrder.asc;
final query = _db.remoteAssetEntity.select().join( final query = _db.remoteAssetEntity.select().join([
[ innerJoin(
innerJoin( _db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id), useColumns: false,
useColumns: false, ),
), ])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId));
],
)..where(
_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId),
);
if (isAscending) { if (isAscending) {
query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]); query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]);
@ -296,69 +245,57 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
} }
TimelineQuery fromAssets(List<BaseAsset> assets) => ( TimelineQuery fromAssets(List<BaseAsset> assets) => (
bucketSource: () => Stream.value(_generateBuckets(assets.length)), bucketSource: () => Stream.value(_generateBuckets(assets.length)),
assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList()), assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList()),
); );
TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) => filter: (row) =>
row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.timeline) & row.ownerId.equals(ownerId), row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.timeline) & row.ownerId.equals(ownerId),
groupBy: groupBy, groupBy: groupBy,
); );
TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) => row.deletedAt.isNull() & row.isFavorite.equals(true) & row.ownerId.equals(userId), filter: (row) => row.deletedAt.isNull() & row.isFavorite.equals(true) & row.ownerId.equals(userId),
groupBy: groupBy, groupBy: groupBy,
); );
TimelineQuery trash(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery trash(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) => row.deletedAt.isNotNull() & row.ownerId.equals(userId), filter: (row) => row.deletedAt.isNotNull() & row.ownerId.equals(userId),
groupBy: groupBy, groupBy: groupBy,
joinLocal: true, joinLocal: true,
); );
TimelineQuery archived(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery archived(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) => filter: (row) =>
row.deletedAt.isNull() & row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.archive), row.deletedAt.isNull() & row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.archive),
groupBy: groupBy, groupBy: groupBy,
); );
TimelineQuery locked(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery locked(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) => filter: (row) =>
row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.locked) & row.ownerId.equals(userId), row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.locked) & row.ownerId.equals(userId),
groupBy: groupBy, groupBy: groupBy,
); );
TimelineQuery video(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( TimelineQuery video(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) => filter: (row) =>
row.deletedAt.isNull() & row.deletedAt.isNull() &
row.type.equalsValue(AssetType.video) & row.type.equalsValue(AssetType.video) &
row.visibility.equalsValue(AssetVisibility.timeline) & row.visibility.equalsValue(AssetVisibility.timeline) &
row.ownerId.equals(userId), row.ownerId.equals(userId),
groupBy: groupBy, groupBy: groupBy,
); );
TimelineQuery place(String place, GroupAssetsBy groupBy) => ( TimelineQuery place(String place, GroupAssetsBy groupBy) => (
bucketSource: () => _watchPlaceBucket( bucketSource: () => _watchPlaceBucket(place, groupBy: groupBy),
place, assetSource: (offset, count) => _getPlaceBucketAssets(place, offset: offset, count: count),
groupBy: groupBy, );
),
assetSource: (offset, count) => _getPlaceBucketAssets(
place,
offset: offset,
count: count,
),
);
Stream<List<Bucket>> _watchPlaceBucket( Stream<List<Bucket>> _watchPlaceBucket(String place, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
String place, {
GroupAssetsBy groupBy = GroupAssetsBy.day,
}) {
if (groupBy == GroupAssetsBy.none) { if (groupBy == GroupAssetsBy.none) {
// TODO: implement GroupAssetBy for place // TODO: implement GroupAssetBy for place
throw UnsupportedError( throw UnsupportedError("GroupAssetsBy.none is not supported for watchPlaceBucket");
"GroupAssetsBy.none is not supported for watchPlaceBucket",
);
} }
final assetCountExp = _db.remoteAssetEntity.id.count(); final assetCountExp = _db.remoteAssetEntity.id.count();
@ -388,27 +325,22 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
}).watch(); }).watch();
} }
Future<List<BaseAsset>> _getPlaceBucketAssets( Future<List<BaseAsset>> _getPlaceBucketAssets(String place, {required int offset, required int count}) {
String place, { final query =
required int offset, _db.remoteAssetEntity.select().join([
required int count, innerJoin(
}) { _db.remoteExifEntity,
final query = _db.remoteAssetEntity.select().join( _db.remoteExifEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
[ useColumns: false,
innerJoin( ),
_db.remoteExifEntity, ])
_db.remoteExifEntity.assetId.equalsExp(_db.remoteAssetEntity.id), ..where(
useColumns: false, _db.remoteAssetEntity.deletedAt.isNull() &
), _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) &
], _db.remoteExifEntity.city.equals(place),
) )
..where( ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
_db.remoteAssetEntity.deletedAt.isNull() & ..limit(count, offset: offset);
_db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) &
_db.remoteExifEntity.city.equals(place),
)
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
..limit(count, offset: offset);
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get();
} }
@ -419,12 +351,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
}) { }) {
return ( return (
bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy), bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy),
assetSource: (offset, count) => _getRemoteAssets( assetSource: (offset, count) =>
filter: filter, _getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal),
offset: offset,
count: count,
joinLocal: joinLocal,
),
); );
} }
@ -460,17 +388,18 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
bool joinLocal = false, bool joinLocal = false,
}) { }) {
if (joinLocal) { if (joinLocal) {
final query = _db.remoteAssetEntity.select().join([ final query =
leftOuterJoin( _db.remoteAssetEntity.select().join([
_db.localAssetEntity, leftOuterJoin(
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), _db.localAssetEntity,
useColumns: false, _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
), useColumns: false,
]) ),
..addColumns([_db.localAssetEntity.id]) ])
..where(filter(_db.remoteAssetEntity)) ..addColumns([_db.localAssetEntity.id])
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) ..where(filter(_db.remoteAssetEntity))
..limit(count, offset: offset); ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
..limit(count, offset: offset);
return query.map((row) { return query.map((row) {
final asset = row.readTable(_db.remoteAssetEntity).toDto(); final asset = row.readTable(_db.remoteAssetEntity).toDto();
@ -507,9 +436,7 @@ extension on Expression<DateTime> {
return switch (groupBy) { return switch (groupBy) {
GroupAssetsBy.day || GroupAssetsBy.auto => localTimeExp.date, GroupAssetsBy.day || GroupAssetsBy.auto => localTimeExp.date,
GroupAssetsBy.month => localTimeExp.strftime("%Y-%m"), GroupAssetsBy.month => localTimeExp.strftime("%Y-%m"),
GroupAssetsBy.none => throw ArgumentError( GroupAssetsBy.none => throw ArgumentError("GroupAssetsBy.none is not supported for date formatting"),
"GroupAssetsBy.none is not supported for date formatting",
),
}; };
} }
} }
@ -519,9 +446,7 @@ extension on String {
final format = switch (groupBy) { final format = switch (groupBy) {
GroupAssetsBy.day || GroupAssetsBy.auto => "y-M-d", GroupAssetsBy.day || GroupAssetsBy.auto => "y-M-d",
GroupAssetsBy.month => "y-M", GroupAssetsBy.month => "y-M",
GroupAssetsBy.none => throw ArgumentError( GroupAssetsBy.none => throw ArgumentError("GroupAssetsBy.none is not supported for date formatting"),
"GroupAssetsBy.none is not supported for date formatting",
),
}; };
try { try {
return DateFormat(format).parse(this); return DateFormat(format).parse(this);

View file

@ -17,15 +17,8 @@ class UserApiRepository extends ApiRepository {
return UserConverter.fromAdminDto(adminDto, preferenceDto); return UserConverter.fromAdminDto(adminDto, preferenceDto);
} }
Future<String> createProfileImage({ Future<String> createProfileImage({required String name, required Uint8List data}) async {
required String name, final res = await checkNull(_api.createProfileImage(MultipartFile.fromBytes('file', data, filename: name)));
required Uint8List data,
}) async {
final res = await checkNull(
_api.createProfileImage(
MultipartFile.fromBytes('file', data, filename: name),
),
);
return res.profileImagePath; return res.profileImagePath;
} }

View file

@ -18,20 +18,8 @@ class DriftUserMetadataRepository extends DriftDatabaseRepository {
extension on UserMetadataEntityData { extension on UserMetadataEntityData {
UserMetadata toDto() => switch (key) { UserMetadata toDto() => switch (key) {
UserMetadataKey.onboarding => UserMetadata( UserMetadataKey.onboarding => UserMetadata(userId: userId, key: key, onboarding: Onboarding.fromMap(value)),
userId: userId, UserMetadataKey.preferences => UserMetadata(userId: userId, key: key, preferences: Preferences.fromMap(value)),
key: key, UserMetadataKey.license => UserMetadata(userId: userId, key: key, license: License.fromMap(value)),
onboarding: Onboarding.fromMap(value), };
),
UserMetadataKey.preferences => UserMetadata(
userId: userId,
key: key,
preferences: Preferences.fromMap(value),
),
UserMetadataKey.license => UserMetadata(
userId: userId,
key: key,
license: License.fromMap(value),
),
};
} }

View file

@ -6,63 +6,59 @@ import 'package:openapi/api.dart';
abstract final class UserConverter { abstract final class UserConverter {
/// Base user dto used where the complete user object is not required /// Base user dto used where the complete user object is not required
static UserDto fromSimpleUserDto(UserResponseDto dto) => UserDto( static UserDto fromSimpleUserDto(UserResponseDto dto) => UserDto(
id: dto.id, id: dto.id,
email: dto.email, email: dto.email,
name: dto.name, name: dto.name,
isAdmin: false, isAdmin: false,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
profileImagePath: dto.profileImagePath, profileImagePath: dto.profileImagePath,
avatarColor: dto.avatarColor.toAvatarColor(), avatarColor: dto.avatarColor.toAvatarColor(),
); );
static UserDto fromAdminDto( static UserDto fromAdminDto(UserAdminResponseDto adminDto, [UserPreferencesResponseDto? preferenceDto]) => UserDto(
UserAdminResponseDto adminDto, [ id: adminDto.id,
UserPreferencesResponseDto? preferenceDto, email: adminDto.email,
]) => name: adminDto.name,
UserDto( isAdmin: adminDto.isAdmin,
id: adminDto.id, updatedAt: adminDto.updatedAt,
email: adminDto.email, profileImagePath: adminDto.profileImagePath,
name: adminDto.name, avatarColor: adminDto.avatarColor.toAvatarColor(),
isAdmin: adminDto.isAdmin, memoryEnabled: preferenceDto?.memories.enabled ?? true,
updatedAt: adminDto.updatedAt, inTimeline: false,
profileImagePath: adminDto.profileImagePath, isPartnerSharedBy: false,
avatarColor: adminDto.avatarColor.toAvatarColor(), isPartnerSharedWith: false,
memoryEnabled: preferenceDto?.memories.enabled ?? true, quotaUsageInBytes: adminDto.quotaUsageInBytes ?? 0,
inTimeline: false, quotaSizeInBytes: adminDto.quotaSizeInBytes ?? 0,
isPartnerSharedBy: false, );
isPartnerSharedWith: false,
quotaUsageInBytes: adminDto.quotaUsageInBytes ?? 0,
quotaSizeInBytes: adminDto.quotaSizeInBytes ?? 0,
);
static UserDto fromPartnerDto(PartnerResponseDto dto) => UserDto( static UserDto fromPartnerDto(PartnerResponseDto dto) => UserDto(
id: dto.id, id: dto.id,
email: dto.email, email: dto.email,
name: dto.name, name: dto.name,
isAdmin: false, isAdmin: false,
updatedAt: DateTime.now(), updatedAt: DateTime.now(),
profileImagePath: dto.profileImagePath, profileImagePath: dto.profileImagePath,
avatarColor: dto.avatarColor.toAvatarColor(), avatarColor: dto.avatarColor.toAvatarColor(),
memoryEnabled: false, memoryEnabled: false,
inTimeline: dto.inTimeline ?? false, inTimeline: dto.inTimeline ?? false,
isPartnerSharedBy: false, isPartnerSharedBy: false,
isPartnerSharedWith: false, isPartnerSharedWith: false,
quotaUsageInBytes: 0, quotaUsageInBytes: 0,
quotaSizeInBytes: 0, quotaSizeInBytes: 0,
); );
} }
extension on UserAvatarColor { extension on UserAvatarColor {
AvatarColor toAvatarColor() => switch (this) { AvatarColor toAvatarColor() => switch (this) {
UserAvatarColor.red => AvatarColor.red, UserAvatarColor.red => AvatarColor.red,
UserAvatarColor.green => AvatarColor.green, UserAvatarColor.green => AvatarColor.green,
UserAvatarColor.blue => AvatarColor.blue, UserAvatarColor.blue => AvatarColor.blue,
UserAvatarColor.purple => AvatarColor.purple, UserAvatarColor.purple => AvatarColor.purple,
UserAvatarColor.orange => AvatarColor.orange, UserAvatarColor.orange => AvatarColor.orange,
UserAvatarColor.pink => AvatarColor.pink, UserAvatarColor.pink => AvatarColor.pink,
UserAvatarColor.amber => AvatarColor.amber, UserAvatarColor.amber => AvatarColor.amber,
UserAvatarColor.yellow => AvatarColor.yellow, UserAvatarColor.yellow => AvatarColor.yellow,
UserAvatarColor.gray => AvatarColor.gray, UserAvatarColor.gray => AvatarColor.gray,
UserAvatarColor.primary || _ => AvatarColor.primary, UserAvatarColor.primary || _ => AvatarColor.primary,
}; };
} }

View file

@ -50,10 +50,7 @@ void main() async {
runApp( runApp(
ProviderScope( ProviderScope(
overrides: [ overrides: [dbProvider.overrideWithValue(db), isarProvider.overrideWithValue(db)],
dbProvider.overrideWithValue(db),
isarProvider.overrideWithValue(db),
],
child: const MainWidget(), child: const MainWidget(),
), ),
); );
@ -100,23 +97,15 @@ Future<void> initApp() async {
globalConfig: (Config.holdingQueue, (1000, 1000, 1000)), globalConfig: (Config.holdingQueue, (1000, 1000, 1000)),
); );
await FileDownloader().trackTasksInGroup( await FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false);
kDownloadGroupLivePhoto,
markDownloadedComplete: false,
);
await FileDownloader().trackTasks(); await FileDownloader().trackTasks();
LicenseRegistry.addLicense( LicenseRegistry.addLicense(() async* {
() async* { for (final license in nonPubLicenses.entries) {
for (final license in nonPubLicenses.entries) { yield LicenseEntryWithLineBreaks([license.key], license.value);
yield LicenseEntryWithLineBreaks( }
[license.key], });
license.value,
);
}
},
);
} }
class ImmichApp extends ConsumerStatefulWidget { class ImmichApp extends ConsumerStatefulWidget {
@ -160,9 +149,7 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
// Sets the navigation bar color // Sets the navigation bar color
SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle( SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent);
systemNavigationBarColor: Colors.transparent,
);
if (Platform.isAndroid) { if (Platform.isAndroid) {
// Android 8 does not support transparent app bars // Android 8 does not support transparent app bars
final info = await DeviceInfoPlugin().androidInfo; final info = await DeviceInfoPlugin().androidInfo;
@ -177,40 +164,22 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
void _configureFileDownloaderNotifications() { void _configureFileDownloaderNotifications() {
FileDownloader().configureNotificationForGroup( FileDownloader().configureNotificationForGroup(
kDownloadGroupImage, kDownloadGroupImage,
running: TaskNotification( running: TaskNotification('downloading_media'.tr(), '${'file_name'.tr()}: {filename}'),
'downloading_media'.tr(), complete: TaskNotification('download_finished'.tr(), '${'file_name'.tr()}: {filename}'),
'${'file_name'.tr()}: {filename}',
),
complete: TaskNotification(
'download_finished'.tr(),
'${'file_name'.tr()}: {filename}',
),
progressBar: true, progressBar: true,
); );
FileDownloader().configureNotificationForGroup( FileDownloader().configureNotificationForGroup(
kDownloadGroupVideo, kDownloadGroupVideo,
running: TaskNotification( running: TaskNotification('downloading_media'.tr(), '${'file_name'.tr()}: {filename}'),
'downloading_media'.tr(), complete: TaskNotification('download_finished'.tr(), '${'file_name'.tr()}: {filename}'),
'${'file_name'.tr()}: {filename}',
),
complete: TaskNotification(
'download_finished'.tr(),
'${'file_name'.tr()}: {filename}',
),
progressBar: true, progressBar: true,
); );
FileDownloader().configureNotificationForGroup( FileDownloader().configureNotificationForGroup(
kManualUploadGroup, kManualUploadGroup,
running: TaskNotification( running: TaskNotification('uploading_media'.tr(), '${'file_name'.tr()}: {displayName}'),
'uploading_media'.tr(), complete: TaskNotification('upload_finished'.tr(), '${'file_name'.tr()}: {displayName}'),
'${'file_name'.tr()}: {displayName}',
),
complete: TaskNotification(
'upload_finished'.tr(),
'${'file_name'.tr()}: {displayName}',
),
progressBar: true, progressBar: true,
); );
} }
@ -222,19 +191,13 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
final isColdStart = currentRouteName == null || currentRouteName == SplashScreenRoute.name; final isColdStart = currentRouteName == null || currentRouteName == SplashScreenRoute.name;
if (deepLink.uri.scheme == "immich") { if (deepLink.uri.scheme == "immich") {
final proposedRoute = await deepLinkHandler.handleScheme( final proposedRoute = await deepLinkHandler.handleScheme(deepLink, isColdStart);
deepLink,
isColdStart,
);
return proposedRoute; return proposedRoute;
} }
if (deepLink.uri.host == "my.immich.app") { if (deepLink.uri.host == "my.immich.app") {
final proposedRoute = await deepLinkHandler.handleMyImmichApp( final proposedRoute = await deepLinkHandler.handleMyImmichApp(deepLink, isColdStart);
deepLink,
isColdStart,
);
return proposedRoute; return proposedRoute;
} }
@ -275,9 +238,7 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
final immichTheme = ref.watch(immichThemeProvider); final immichTheme = ref.watch(immichThemeProvider);
return ProviderScope( return ProviderScope(
overrides: [ overrides: [localeProvider.overrideWithValue(context.locale)],
localeProvider.overrideWithValue(context.locale),
],
child: MaterialApp.router( child: MaterialApp.router(
title: 'Immich', title: 'Immich',
debugShowCheckedModeBanner: true, debugShowCheckedModeBanner: true,
@ -285,14 +246,8 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
supportedLocales: context.supportedLocales, supportedLocales: context.supportedLocales,
locale: context.locale, locale: context.locale,
themeMode: ref.watch(immichThemeModeProvider), themeMode: ref.watch(immichThemeModeProvider),
darkTheme: getThemeData( darkTheme: getThemeData(colorScheme: immichTheme.dark, locale: context.locale),
colorScheme: immichTheme.dark, theme: getThemeData(colorScheme: immichTheme.light, locale: context.locale),
locale: context.locale,
),
theme: getThemeData(
colorScheme: immichTheme.light,
locale: context.locale,
),
routerConfig: router.config( routerConfig: router.config(
deepLinkBuilder: _deepLinkBuilder, deepLinkBuilder: _deepLinkBuilder,
navigatorObservers: () => [AppNavigationObserver(ref: ref), HeroController()], navigatorObservers: () => [AppNavigationObserver(ref: ref), HeroController()],

View file

@ -7,15 +7,9 @@ class AlbumAddAssetsResponse {
List<String> alreadyInAlbum; List<String> alreadyInAlbum;
int successfullyAdded; int successfullyAdded;
AlbumAddAssetsResponse({ AlbumAddAssetsResponse({required this.alreadyInAlbum, required this.successfullyAdded});
required this.alreadyInAlbum,
required this.successfullyAdded,
});
AlbumAddAssetsResponse copyWith({ AlbumAddAssetsResponse copyWith({List<String>? alreadyInAlbum, int? successfullyAdded}) {
List<String>? alreadyInAlbum,
int? successfullyAdded,
}) {
return AlbumAddAssetsResponse( return AlbumAddAssetsResponse(
alreadyInAlbum: alreadyInAlbum ?? this.alreadyInAlbum, alreadyInAlbum: alreadyInAlbum ?? this.alreadyInAlbum,
successfullyAdded: successfullyAdded ?? this.successfullyAdded, successfullyAdded: successfullyAdded ?? this.successfullyAdded,
@ -23,10 +17,7 @@ class AlbumAddAssetsResponse {
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap() {
return <String, dynamic>{ return <String, dynamic>{'alreadyInAlbum': alreadyInAlbum, 'successfullyAdded': successfullyAdded};
'alreadyInAlbum': alreadyInAlbum,
'successfullyAdded': successfullyAdded,
};
} }
String toJson() => json.encode(toMap()); String toJson() => json.encode(toMap());

View file

@ -1,5 +1 @@
enum QuickFilterMode { enum QuickFilterMode { all, sharedWithMe, myAlbums }
all,
sharedWithMe,
myAlbums,
}

View file

@ -11,11 +11,7 @@ class AlbumViewerPageState {
required this.editDescriptionText, required this.editDescriptionText,
}); });
AlbumViewerPageState copyWith({ AlbumViewerPageState copyWith({bool? isEditAlbum, String? editTitleText, String? editDescriptionText}) {
bool? isEditAlbum,
String? editTitleText,
String? editDescriptionText,
}) {
return AlbumViewerPageState( return AlbumViewerPageState(
isEditAlbum: isEditAlbum ?? this.isEditAlbum, isEditAlbum: isEditAlbum ?? this.isEditAlbum,
editTitleText: editTitleText ?? this.editTitleText, editTitleText: editTitleText ?? this.editTitleText,

View file

@ -4,9 +4,7 @@ import 'package:immich_mobile/entities/asset.entity.dart';
class AssetSelectionPageResult { class AssetSelectionPageResult {
final Set<Asset> selectedAssets; final Set<Asset> selectedAssets;
const AssetSelectionPageResult({ const AssetSelectionPageResult({required this.selectedAssets});
required this.selectedAssets,
});
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;

View file

@ -13,12 +13,7 @@ class AssetSelectionState {
this.selectedCount = 0, this.selectedCount = 0,
}); });
AssetSelectionState copyWith({ AssetSelectionState copyWith({bool? hasRemote, bool? hasLocal, bool? hasMerged, int? selectedCount}) {
bool? hasRemote,
bool? hasLocal,
bool? hasMerged,
int? selectedCount,
}) {
return AssetSelectionState( return AssetSelectionState(
hasRemote: hasRemote ?? this.hasRemote, hasRemote: hasRemote ?? this.hasRemote,
hasLocal: hasLocal ?? this.hasLocal, hasLocal: hasLocal ?? this.hasLocal,
@ -28,10 +23,10 @@ class AssetSelectionState {
} }
AssetSelectionState.fromSelection(Set<Asset> selection) AssetSelectionState.fromSelection(Set<Asset> selection)
: hasLocal = selection.any((e) => e.storage == AssetState.local), : hasLocal = selection.any((e) => e.storage == AssetState.local),
hasMerged = selection.any((e) => e.storage == AssetState.merged), hasMerged = selection.any((e) => e.storage == AssetState.merged),
hasRemote = selection.any((e) => e.storage == AssetState.remote), hasRemote = selection.any((e) => e.storage == AssetState.remote),
selectedCount = selection.length; selectedCount = selection.length;
@override @override
String toString() => String toString() =>

Some files were not shown because too many files have changed in this diff Show more