drift(mobile): drift auth user sync

This commit is contained in:
wuzihao051119
2025-07-25 12:28:02 +08:00
parent ad65e9011a
commit 4677ceb03c
69 changed files with 9116 additions and 1206 deletions
@@ -6,6 +6,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/presentation/pages/dev/dev_logger.dart';
@@ -13,7 +14,7 @@ import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/routing/router.dart';
final _features = [
@@ -26,12 +27,14 @@ final _features = [
name: 'Selection Mode Timeline',
icon: Icons.developer_mode_rounded,
onTap: (ctx, ref) async {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
return Future.value();
}
final assets = await ref.read(remoteAssetRepositoryProvider).getSome(user.id);
final assets = await ref.read(remoteAssetRepositoryProvider).getSome(user!.id);
final selectedAssets = await ctx.pushRoute<Set<BaseAsset>>(
DriftAssetSelectionTimelineRoute(
@@ -6,6 +6,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
@@ -14,7 +15,7 @@ import 'package:immich_mobile/pages/common/large_leading_tile.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/remote_album.utils.dart';
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
@@ -49,8 +50,9 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
}
void onSearch(String searchTerm, QuickFilterMode sortMode) {
final userId = ref.watch(currentUserProvider)?.id;
ref.read(remoteAlbumProvider.notifier).searchAlbums(searchTerm, userId, sortMode);
ref.watch(currentUserNotifierProvider).whenData(
(user) => ref.read(remoteAlbumProvider.notifier).searchAlbums(searchTerm, user?.id, sortMode),
);
}
Future<void> onRefresh() async {
@@ -88,7 +90,7 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
Widget build(BuildContext context) {
final albums = ref.watch(remoteAlbumProvider.select((s) => s.filteredAlbums));
final userId = ref.watch(currentUserProvider)?.id;
final user = ref.watch(currentUserNotifierProvider);
return RefreshIndicator(
onRefresh: onRefresh,
@@ -129,15 +131,17 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
isGrid: isGrid,
onToggleViewMode: toggleViewMode,
),
isGrid
? _AlbumGrid(
albums: albums,
userId: userId,
)
: _AlbumList(
albums: albums,
userId: userId,
),
user.widgetWhen(
onData: (user) => isGrid
? _AlbumGrid(
albums: albums,
userId: user?.id,
)
: _AlbumList(
albums: albums,
userId: user?.id,
),
),
],
),
);
@@ -1,11 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
@@ -18,12 +19,14 @@ class DriftArchivePage extends StatelessWidget {
overrides: [
timelineServiceProvider.overrideWith(
(ref) {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
throw Exception('User must be logged in to access archive');
}
final timelineService = ref.watch(timelineFactoryProvider).archive(user.id);
final timelineService = ref.watch(timelineFactoryProvider).archive(user!.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
@@ -2,10 +2,11 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
@RoutePage()
class DriftAssetSelectionTimelinePage extends ConsumerWidget {
@@ -30,14 +31,16 @@ class DriftAssetSelectionTimelinePage extends ConsumerWidget {
),
timelineServiceProvider.overrideWith(
(ref) {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
throw Exception(
'User must be logged in to access asset selection timeline',
);
}
final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user.id);
final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user!.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
@@ -1,11 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
@@ -18,12 +19,14 @@ class DriftFavoritePage extends StatelessWidget {
overrides: [
timelineServiceProvider.overrideWith(
(ref) {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
throw Exception('User must be logged in to access favorite');
}
final timelineService = ref.watch(timelineFactoryProvider).favorite(user.id);
final timelineService = ref.watch(timelineFactoryProvider).favorite(user!.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
@@ -475,7 +475,7 @@ class _QuickAccessButtonList extends ConsumerWidget {
class _PartnerList extends StatelessWidget {
const _PartnerList({required this.partners});
final List<PartnerUserDto> partners;
final List<PartnerUser> partners;
@override
Widget build(BuildContext context) {
@@ -1,12 +1,13 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
@@ -47,12 +48,14 @@ class _DriftLockedFolderPageState extends ConsumerState<DriftLockedFolderPage> w
overrides: [
timelineServiceProvider.overrideWith(
(ref) {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
throw Exception('User must be logged in to access locked folder');
}
final timelineService = ref.watch(timelineFactoryProvider).lockedFolder(user.id);
final timelineService = ref.watch(timelineFactoryProvider).lockedFolder(user!.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
@@ -7,13 +7,12 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/partner_detail_b
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
class DriftPartnerDetailPage extends StatelessWidget {
final PartnerUserDto partner;
final PartnerUser partner;
const DriftPartnerDetailPage({
super.key,
@@ -46,7 +45,7 @@ class DriftPartnerDetailPage extends StatelessWidget {
}
class _InfoBox extends ConsumerStatefulWidget {
final PartnerUserDto partner;
final PartnerUser partner;
const _InfoBox({
required this.partner,
@@ -66,7 +65,9 @@ class _InfoBoxState extends ConsumerState<_InfoBox> {
}
_toggleInTimeline() async {
final user = ref.read(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
return;
}
@@ -74,7 +75,7 @@ class _InfoBoxState extends ConsumerState<_InfoBox> {
try {
await ref.read(partnerUsersProvider.notifier).toggleShowInTimeline(
widget.partner.id,
user.id,
user!.id,
);
setState(() {
@@ -1,10 +1,11 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
@@ -17,14 +18,16 @@ class DriftRecentlyTakenPage extends StatelessWidget {
overrides: [
timelineServiceProvider.overrideWith(
(ref) {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
throw Exception(
'User must be logged in to access recently taken',
);
}
final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user.id);
final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user!.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart';
@@ -12,7 +13,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/common/remote_album_sliver_app_bar.dart';
@@ -186,8 +187,9 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
}
void showOptionSheet(BuildContext context) {
final user = ref.watch(currentUserProvider);
final isOwner = user != null ? user.id == widget.album.ownerId : false;
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
final isOwner = user != null ? user!.id == widget.album.ownerId : false;
showModalBottomSheet(
context: context,
@@ -1,12 +1,13 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
@RoutePage()
class DriftTrashPage extends StatelessWidget {
@@ -18,12 +19,14 @@ class DriftTrashPage extends StatelessWidget {
overrides: [
timelineServiceProvider.overrideWith(
(ref) {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
throw Exception('User must be logged in to access trash');
}
final timelineService = ref.watch(timelineFactoryProvider).trash(user.id);
final timelineService = ref.watch(timelineFactoryProvider).trash(user!.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},
@@ -5,40 +5,21 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/models/album/album.model.dart';
import 'package:immich_mobile/domain/models/user_metadata.model.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/widgets/common/drift_user_circle_avatar.dart';
// TODO: Refactor this provider when we have user provider/service/repository pattern in place
final driftUsersProvider = FutureProvider.autoDispose<List<UserDto>>((ref) async {
final driftUsersProvider = FutureProvider.autoDispose<List<User>>((ref) async {
final drift = ref.watch(driftProvider);
final currentUser = ref.watch(currentUserProvider);
User? currentUser;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => currentUser = asyncUser);
final userEntities = await drift.managers.userEntity.get();
final users = userEntities
.map(
(entity) => UserDto(
id: entity.id,
name: entity.name,
email: entity.email,
isAdmin: entity.isAdmin,
profileImagePath: entity.profileImagePath,
updatedAt: entity.updatedAt,
quotaSizeInBytes: entity.quotaSizeInBytes ?? 0,
quotaUsageInBytes: entity.quotaUsageInBytes,
isPartnerSharedBy: false,
isPartnerSharedWith: false,
avatarColor: AvatarColor.primary,
memoryEnabled: true,
inTimeline: true,
),
)
.toList();
final users = await drift.managers.userEntity.map((row) => row.toDto()).get();
users.removeWhere((u) => currentUser?.id == u.id);
@@ -56,14 +37,14 @@ class DriftUserSelectionPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final AsyncValue<List<UserDto>> suggestedShareUsers = ref.watch(driftUsersProvider);
final sharedUsersList = useState<Set<UserDto>>({});
final AsyncValue<List<User>> suggestedShareUsers = ref.watch(driftUsersProvider);
final sharedUsersList = useState<Set<User>>({});
addNewUsersHandler() {
context.maybePop(sharedUsersList.value.map((e) => e.id).toList());
}
buildTileIcon(UserDto user) {
buildTileIcon(User user) {
if (sharedUsersList.value.contains(user)) {
return CircleAvatar(
backgroundColor: context.primaryColor,
@@ -73,13 +54,13 @@ class DriftUserSelectionPage extends HookConsumerWidget {
),
);
} else {
return UserCircleAvatar(
return DriftUserCircleAvatar(
user: user,
);
}
}
buildUserList(List<UserDto> users) {
buildUserList(List<User> users) {
List<Widget> usersChip = [];
for (var user in sharedUsersList.value) {
@@ -1,10 +1,11 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
@RoutePage()
@@ -17,12 +18,14 @@ class DriftVideoPage extends StatelessWidget {
overrides: [
timelineServiceProvider.overrideWith(
(ref) {
final user = ref.watch(currentUserProvider);
User? user;
ref.watch(currentUserNotifierProvider).whenData((asyncUser) => user = asyncUser);
if (user == null) {
throw Exception('User must be logged in to video');
}
final timelineService = ref.watch(timelineFactoryProvider).video(user.id);
final timelineService = ref.watch(timelineFactoryProvider).video(user!.id);
ref.onDispose(timelineService.dispose);
return timelineService;
},