feature(mobile): sync assets, albums & users to local database on device (#1759)

* feature(mobile): sync assets, albums & users to local database on device

* try to fix tests

* move DB sync operations to new SyncService

* clear db on user logout

* fix reason for endless loading timeline

* fix error when deleting album

* fix thumbnail of device albums

* add a few comments

* fix Hive box not open in album service when loading local assets

* adjust tests to int IDs

* fix bug: show all albums when Recent is selected

* update generated api

* reworked Recents album isAll handling

* guard against wrongly interleaved sync operations

* fix: timeline asset ordering (sort asset state by created at)

* fix: sort assets in albums by created at
This commit is contained in:
Fynn Petersen-Frey
2023-03-03 23:38:30 +01:00
committed by GitHub
parent 8f11529a75
commit 8708867c1c
61 changed files with 9024 additions and 893 deletions

View File

@@ -13,14 +13,16 @@ void main() {
testAssets.add(
Asset(
deviceAssetId: '$i',
deviceId: '',
ownerId: '',
localId: '$i',
deviceId: 1,
ownerId: 1,
fileCreatedAt: date,
fileModifiedAt: date,
updatedAt: date,
durationInSeconds: 0,
fileName: '',
isFavorite: false,
isLocal: false,
),
);
}

View File

@@ -0,0 +1,50 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:immich_mobile/utils/diff.dart';
void main() {
final List<int> listA = [1, 2, 3, 4, 6];
final List<int> listB = [1, 3, 5, 7];
group('Test grouped', () {
test('test partial overlap', () async {
final List<int> onlyInA = [];
final List<int> onlyInB = [];
final List<int> inBoth = [];
final changes = await diffSortedLists(
listA,
listB,
compare: (int a, int b) => a.compareTo(b),
both: (int a, int b) {
inBoth.add(b);
return false;
},
onlyFirst: (int a) => onlyInA.add(a),
onlySecond: (int b) => onlyInB.add(b),
);
expect(changes, true);
expect(onlyInA, [2, 4, 6]);
expect(onlyInB, [5, 7]);
expect(inBoth, [1, 3]);
});
test('test partial overlap sync', () {
final List<int> onlyInA = [];
final List<int> onlyInB = [];
final List<int> inBoth = [];
final changes = diffSortedListsSync(
listA,
listB,
compare: (int a, int b) => a.compareTo(b),
both: (int a, int b) {
inBoth.add(b);
return false;
},
onlyFirst: (int a) => onlyInA.add(a),
onlySecond: (int b) => onlyInB.add(b),
);
expect(changes, true);
expect(onlyInA, [2, 4, 6]);
expect(onlyInB, [5, 7]);
expect(inBoth, [1, 3]);
});
});
}

View File

@@ -12,75 +12,81 @@ import 'package:mockito/mockito.dart';
])
import 'favorite_provider_test.mocks.dart';
Asset _getTestAsset(String id, bool favorite) {
return Asset(
remoteId: id,
deviceAssetId: '',
deviceId: '',
ownerId: '',
Asset _getTestAsset(int id, bool favorite) {
final Asset a = Asset(
remoteId: id.toString(),
localId: id.toString(),
deviceId: 1,
ownerId: 1,
fileCreatedAt: DateTime.now(),
fileModifiedAt: DateTime.now(),
updatedAt: DateTime.now(),
isLocal: false,
durationInSeconds: 0,
fileName: '',
isFavorite: favorite,
);
a.id = id;
return a;
}
void main() {
group("Test favoriteProvider", () {
late MockAssetsState assetsState;
late MockAssetNotifier assetNotifier;
late ProviderContainer container;
late StateNotifierProvider<FavoriteSelectionNotifier, Set<String>> testFavoritesProvider;
late StateNotifierProvider<FavoriteSelectionNotifier, Set<int>>
testFavoritesProvider;
setUp(() {
assetsState = MockAssetsState();
assetNotifier = MockAssetNotifier();
container = ProviderContainer();
setUp(
() {
assetsState = MockAssetsState();
assetNotifier = MockAssetNotifier();
container = ProviderContainer();
testFavoritesProvider =
StateNotifierProvider<FavoriteSelectionNotifier, Set<String>>((ref) {
return FavoriteSelectionNotifier(
assetsState,
assetNotifier,
);
});
},);
testFavoritesProvider =
StateNotifierProvider<FavoriteSelectionNotifier, Set<int>>((ref) {
return FavoriteSelectionNotifier(
assetsState,
assetNotifier,
);
});
},
);
test("Empty favorites provider", () {
when(assetsState.allAssets).thenReturn([]);
expect(<String>{}, container.read(testFavoritesProvider));
expect(<int>{}, container.read(testFavoritesProvider));
});
test("Non-empty favorites provider", () {
when(assetsState.allAssets).thenReturn([
_getTestAsset("001", false),
_getTestAsset("002", true),
_getTestAsset("003", false),
_getTestAsset("004", false),
_getTestAsset("005", true),
_getTestAsset(1, false),
_getTestAsset(2, true),
_getTestAsset(3, false),
_getTestAsset(4, false),
_getTestAsset(5, true),
]);
expect(<String>{"002", "005"}, container.read(testFavoritesProvider));
expect(<int>{2, 5}, container.read(testFavoritesProvider));
});
test("Toggle favorite", () {
when(assetNotifier.toggleFavorite(null, false))
.thenAnswer((_) async => false);
final testAsset1 = _getTestAsset("001", false);
final testAsset2 = _getTestAsset("002", true);
final testAsset1 = _getTestAsset(1, false);
final testAsset2 = _getTestAsset(2, true);
when(assetsState.allAssets).thenReturn([testAsset1, testAsset2]);
expect(<String>{"002"}, container.read(testFavoritesProvider));
expect(<int>{2}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset2);
expect(<String>{}, container.read(testFavoritesProvider));
expect(<int>{}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset1);
expect(<String>{"001"}, container.read(testFavoritesProvider));
expect(<int>{1}, container.read(testFavoritesProvider));
});
test("Add favorites", () {
@@ -89,16 +95,16 @@ void main() {
when(assetsState.allAssets).thenReturn([]);
expect(<String>{}, container.read(testFavoritesProvider));
expect(<int>{}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).addToFavorites(
[
_getTestAsset("001", false),
_getTestAsset("002", false),
_getTestAsset(1, false),
_getTestAsset(2, false),
],
);
expect(<String>{"001", "002"}, container.read(testFavoritesProvider));
expect(<int>{1, 2}, container.read(testFavoritesProvider));
});
});
}

View File

@@ -187,7 +187,7 @@ class MockAssetNotifier extends _i1.Mock implements _i2.AssetNotifier {
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
void onNewAssetUploaded(_i4.Asset? newAsset) => super.noSuchMethod(
Future<void> onNewAssetUploaded(_i4.Asset? newAsset) => super.noSuchMethod(
Invocation.method(
#onNewAssetUploaded,
[newAsset],
@@ -195,7 +195,7 @@ class MockAssetNotifier extends _i1.Mock implements _i2.AssetNotifier {
returnValueForMissingStub: null,
);
@override
dynamic deleteAssets(Set<_i4.Asset>? deleteAssets) => super.noSuchMethod(
Future<void> deleteAssets(Set<_i4.Asset> deleteAssets) => super.noSuchMethod(
Invocation.method(
#deleteAssets,
[deleteAssets],