mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feature(mobile): Hardening synchronization mechanism + Pull to refresh (#2085)
* fix(mobile): allow syncing duplicate local IDs * enable to run isar unit tests on CI * serialize sync operations, add pull to refresh on timeline --------- Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
This commit is contained in:
parent
1a94530935
commit
cae37657e9
21 changed files with 653 additions and 249 deletions
|
|
@ -53,7 +53,7 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||
// Add the owner name to the subtitle
|
||||
String? owner;
|
||||
if (showOwner) {
|
||||
if (album.ownerId == Store.get(StoreKey.userRemoteId)) {
|
||||
if (album.ownerId == Store.get(StoreKey.currentUser).id) {
|
||||
owner = 'album_thumbnail_owned'.tr();
|
||||
} else if (album.ownerName != null) {
|
||||
owner = 'album_thumbnail_shared_by'.tr(args: [album.ownerName!]);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class SharingPage extends HookConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final List<Album> sharedAlbums = ref.watch(sharedAlbumProvider);
|
||||
final userId = store.Store.get(store.StoreKey.userRemoteId);
|
||||
final userId = store.Store.get(store.StoreKey.currentUser).id;
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
useEffect(
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||
final bool selectionActive;
|
||||
final List<Asset> assets;
|
||||
final RenderList? renderList;
|
||||
final Future<void> Function()? onRefresh;
|
||||
|
||||
const ImmichAssetGrid({
|
||||
super.key,
|
||||
required this.assets,
|
||||
this.onRefresh,
|
||||
this.renderList,
|
||||
this.assetsPerRow,
|
||||
this.showStorageIndicator,
|
||||
|
|
@ -62,11 +64,12 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||
enabled: enableHeroAnimations.value,
|
||||
child: ImmichAssetGridView(
|
||||
allAssets: assets,
|
||||
assetsPerRow: assetsPerRow
|
||||
?? settings.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
onRefresh: onRefresh,
|
||||
assetsPerRow: assetsPerRow ??
|
||||
settings.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
listener: listener,
|
||||
showStorageIndicator: showStorageIndicator
|
||||
?? settings.getSetting(AppSettingsEnum.storageIndicator),
|
||||
showStorageIndicator: showStorageIndicator ??
|
||||
settings.getSetting(AppSettingsEnum.storageIndicator),
|
||||
renderList: renderList!,
|
||||
margin: margin,
|
||||
selectionActive: selectionActive,
|
||||
|
|
@ -76,26 +79,25 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
return renderListFuture.when(
|
||||
data: (renderList) =>
|
||||
WillPopScope(
|
||||
onWillPop: onWillPop,
|
||||
child: HeroMode(
|
||||
enabled: enableHeroAnimations.value,
|
||||
child: ImmichAssetGridView(
|
||||
allAssets: assets,
|
||||
assetsPerRow: assetsPerRow
|
||||
?? settings.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
listener: listener,
|
||||
showStorageIndicator: showStorageIndicator
|
||||
?? settings.getSetting(AppSettingsEnum.storageIndicator),
|
||||
renderList: renderList,
|
||||
margin: margin,
|
||||
selectionActive: selectionActive,
|
||||
),
|
||||
data: (renderList) => WillPopScope(
|
||||
onWillPop: onWillPop,
|
||||
child: HeroMode(
|
||||
enabled: enableHeroAnimations.value,
|
||||
child: ImmichAssetGridView(
|
||||
allAssets: assets,
|
||||
onRefresh: onRefresh,
|
||||
assetsPerRow: assetsPerRow ??
|
||||
settings.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
listener: listener,
|
||||
showStorageIndicator: showStorageIndicator ??
|
||||
settings.getSetting(AppSettingsEnum.storageIndicator),
|
||||
renderList: renderList,
|
||||
margin: margin,
|
||||
selectionActive: selectionActive,
|
||||
),
|
||||
),
|
||||
error: (err, stack) =>
|
||||
Center(child: Text("$err")),
|
||||
),
|
||||
error: (err, stack) => Center(child: Text("$err")),
|
||||
loading: () => const Center(
|
||||
child: ImmichLoadingIndicator(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -199,21 +199,23 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
|||
addRepaintBoundaries: true,
|
||||
);
|
||||
|
||||
if (!useDragScrolling) {
|
||||
return listWidget;
|
||||
}
|
||||
final child = useDragScrolling
|
||||
? DraggableScrollbar.semicircle(
|
||||
scrollStateListener: dragScrolling,
|
||||
itemPositionsListener: _itemPositionsListener,
|
||||
controller: _itemScrollController,
|
||||
backgroundColor: Theme.of(context).hintColor,
|
||||
labelTextBuilder: _labelBuilder,
|
||||
labelConstraints: const BoxConstraints(maxHeight: 28),
|
||||
scrollbarAnimationDuration: const Duration(seconds: 1),
|
||||
scrollbarTimeToFade: const Duration(seconds: 4),
|
||||
child: listWidget,
|
||||
)
|
||||
: listWidget;
|
||||
|
||||
return DraggableScrollbar.semicircle(
|
||||
scrollStateListener: dragScrolling,
|
||||
itemPositionsListener: _itemPositionsListener,
|
||||
controller: _itemScrollController,
|
||||
backgroundColor: Theme.of(context).hintColor,
|
||||
labelTextBuilder: _labelBuilder,
|
||||
labelConstraints: const BoxConstraints(maxHeight: 28),
|
||||
scrollbarAnimationDuration: const Duration(seconds: 1),
|
||||
scrollbarTimeToFade: const Duration(seconds: 4),
|
||||
child: listWidget,
|
||||
);
|
||||
return widget.onRefresh == null
|
||||
? child
|
||||
: RefreshIndicator(onRefresh: widget.onRefresh!, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -248,7 +250,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
|
|||
}
|
||||
|
||||
void _scrollToTop() {
|
||||
// for some reason, this is necessary as well in order
|
||||
// for some reason, this is necessary as well in order
|
||||
// to correctly reposition the drag thumb scroll bar
|
||||
_itemScrollController.jumpTo(
|
||||
index: 0,
|
||||
|
|
@ -281,6 +283,7 @@ class ImmichAssetGridView extends StatefulWidget {
|
|||
final ImmichAssetGridSelectionListener? listener;
|
||||
final bool selectionActive;
|
||||
final List<Asset> allAssets;
|
||||
final Future<void> Function()? onRefresh;
|
||||
|
||||
const ImmichAssetGridView({
|
||||
super.key,
|
||||
|
|
@ -291,6 +294,7 @@ class ImmichAssetGridView extends StatefulWidget {
|
|||
this.listener,
|
||||
this.margin = 5.0,
|
||||
this.selectionActive = false,
|
||||
this.onRefresh,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class HomePage extends HookConsumerWidget {
|
|||
final albumService = ref.watch(albumServiceProvider);
|
||||
|
||||
final tipOneOpacity = useState(0.0);
|
||||
final refreshCount = useState(0);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
|
|
@ -182,6 +183,22 @@ class HomePage extends HookConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> refreshAssets() async {
|
||||
debugPrint("refreshCount.value ${refreshCount.value}");
|
||||
final fullRefresh = refreshCount.value > 0;
|
||||
await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh);
|
||||
if (fullRefresh) {
|
||||
// refresh was forced: user requested another refresh within 2 seconds
|
||||
refreshCount.value = 0;
|
||||
} else {
|
||||
refreshCount.value++;
|
||||
// set counter back to 0 if user does not request refresh again
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
refreshCount.value = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
buildLoadingIndicator() {
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
tipOneOpacity.value = 1;
|
||||
|
|
@ -241,6 +258,7 @@ class HomePage extends HookConsumerWidget {
|
|||
.getSetting(AppSettingsEnum.storageIndicator),
|
||||
listener: selectionListener,
|
||||
selectionActive: selectionEnabledHook.value,
|
||||
onRefresh: refreshAssets,
|
||||
),
|
||||
if (selectionEnabledHook.value)
|
||||
SafeArea(
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||
await Future.wait([
|
||||
_apiService.authenticationApi.logout(),
|
||||
Store.delete(StoreKey.assetETag),
|
||||
Store.delete(StoreKey.userRemoteId),
|
||||
Store.delete(StoreKey.currentUser),
|
||||
Store.delete(StoreKey.accessToken),
|
||||
]);
|
||||
|
|
@ -133,7 +132,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||
var deviceInfo = await _deviceInfoService.getDeviceInfo();
|
||||
Store.put(StoreKey.deviceId, deviceInfo["deviceId"]);
|
||||
Store.put(StoreKey.deviceIdHash, fastHash(deviceInfo["deviceId"]));
|
||||
Store.put(StoreKey.userRemoteId, userResponseDto.id);
|
||||
Store.put(StoreKey.currentUser, User.fromDto(userResponseDto));
|
||||
Store.put(StoreKey.serverUrl, serverUrl);
|
||||
Store.put(StoreKey.accessToken, accessToken);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue