fix: riverpod reactivity regression from lint fixes (#30734)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2026-08-12 19:30:06 +05:30 committed by GitHub
parent 610bcfa6d0
commit ce705dcc06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 100 additions and 52 deletions

View file

@ -316,12 +316,11 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final backupAlbums = ref.watch(backupAlbumProvider);
Widget buildSelectedAlbumName() { Widget buildSelectedAlbumName() {
String text = context.t.backup_controller_page_backup_selected; String text = context.t.backup_controller_page_backup_selected;
final albums = ref final albums = backupAlbums.where((album) => album.backupSelection == BackupSelection.selected).toList();
.read(backupAlbumProvider)
.where((album) => album.backupSelection == BackupSelection.selected)
.toList();
if (albums.isNotEmpty) { if (albums.isNotEmpty) {
for (var album in albums) { for (var album in albums) {
@ -352,10 +351,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget {
Widget buildExcludedAlbumName() { Widget buildExcludedAlbumName() {
String text = context.t.backup_controller_page_excluded; String text = context.t.backup_controller_page_excluded;
final albums = ref final albums = backupAlbums.where((album) => album.backupSelection == BackupSelection.excluded).toList();
.read(backupAlbumProvider)
.where((album) => album.backupSelection == BackupSelection.excluded)
.toList();
if (albums.isNotEmpty) { if (albums.isNotEmpty) {
for (var album in albums) { for (var album in albums) {

View file

@ -32,6 +32,8 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
final userId = ref.watch(authProvider).userId; final userId = ref.watch(authProvider).userId;
final activityEnabled = useState(album.isActivityEnabled); final activityEnabled = useState(album.isActivityEnabled);
final isOwner = album.ownerId == userId; final isOwner = album.ownerId == userId;
final owner = isOwner ? ref.watch(currentUserProvider) : null;
final allUsers = isOwner ? null : ref.watch(driftUsersProvider);
void showErrorMessage() { void showErrorMessage() {
ContextHelper(context).pop(); ContextHelper(context).pop();
@ -141,7 +143,6 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
Widget buildOwnerInfo() { Widget buildOwnerInfo() {
if (isOwner) { if (isOwner) {
final owner = ref.read(currentUserProvider);
return ListTile( return ListTile(
leading: owner != null ? UserCircleAvatar(user: owner) : const SizedBox(), leading: owner != null ? UserCircleAvatar(user: owner) : const SizedBox(),
title: Text(album.ownerName, style: const TextStyle(fontWeight: FontWeight.w500)), title: Text(album.ownerName, style: const TextStyle(fontWeight: FontWeight.w500)),
@ -149,8 +150,11 @@ class DriftAlbumOptionsPage extends HookConsumerWidget {
trailing: Text(context.t.owner, style: context.textTheme.labelLarge), trailing: Text(context.t.owner, style: context.textTheme.labelLarge),
); );
} else { } else {
final usersProvider = ref.read(driftUsersProvider); if (allUsers == null) {
return usersProvider.maybeWhen( return const SizedBox();
}
return allUsers.maybeWhen(
data: (users) { data: (users) {
final user = users.firstWhereOrNull((u) => u.id == album.ownerId); final user = users.firstWhereOrNull((u) => u.id == album.ownerId);

View file

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/config/slideshow_config.dart'; import 'package:immich_mobile/domain/models/config/slideshow_config.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
@ -295,14 +296,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
color: context.colorScheme.primary, color: context.colorScheme.primary,
); );
} else { } else {
return LinearProgressIndicator( return _VideoProgressBar(asset: asset);
color: context.colorScheme.primary,
borderRadius: BorderRadius.zero,
minHeight: 5,
value:
ref.read(videoPlayerProvider(asset.id).select((s) => s.position)).inMilliseconds /
asset.duration.inMilliseconds,
);
} }
} }
@ -374,25 +368,13 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
builder: (context, value, _) => buildPhotoView(scale * (1.0 + value * _kenBurnsZoom)), builder: (context, value, _) => buildPhotoView(scale * (1.0 + value * _kenBurnsZoom)),
); );
} else { } else {
final status = ref.read(videoPlayerProvider(asset.id).select((s) => s.status)); return _VideoChild(
final position = ref.read(videoPlayerProvider(asset.id)).position; asset: asset,
isCurrent: isCurrent,
if (status == VideoPlaybackStatus.completed && isCurrent && position.inMicroseconds > 0) { scale: scale,
unawaited(_nextPage()); imageProvider: imageProvider,
} else if (status == VideoPlaybackStatus.playing) { onTapUp: _onTapUp,
unawaited(ref.read(videoPlayerProvider(asset.id).notifier).setLoop(false)); onCompleted: _nextPage,
}
return PhotoView.customChild(
onTapUp: (_, _, _) => _onTapUp(),
disableScaleGestures: true,
filterQuality: FilterQuality.high,
initialScale: scale,
child: NativeVideoViewer(
asset: asset,
isCurrent: isCurrent,
image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center),
),
); );
} }
} }
@ -477,6 +459,67 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
} }
} }
class _VideoChild extends ConsumerWidget {
final BaseAsset asset;
final bool isCurrent;
final PhotoViewComputedScale scale;
final ImageProvider imageProvider;
final VoidCallback onTapUp;
final VoidCallback onCompleted;
const _VideoChild({
required this.asset,
required this.isCurrent,
required this.scale,
required this.imageProvider,
required this.onTapUp,
required this.onCompleted,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen(videoPlayerProvider(asset.id).select((s) => s.status), (_, status) {
if (status == VideoPlaybackStatus.completed) {
if (isCurrent && ref.read(videoPlayerProvider(asset.id)).position.inMicroseconds > 0) {
onCompleted();
}
} else if (status == VideoPlaybackStatus.playing) {
unawaited(ref.read(videoPlayerProvider(asset.id).notifier).setLoop(false));
}
});
return PhotoView.customChild(
onTapUp: (_, _, _) => onTapUp(),
disableScaleGestures: true,
filterQuality: FilterQuality.high,
initialScale: scale,
child: NativeVideoViewer(
asset: asset,
isCurrent: isCurrent,
image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center),
),
);
}
}
class _VideoProgressBar extends ConsumerWidget {
final BaseAsset asset;
const _VideoProgressBar({required this.asset});
@override
Widget build(BuildContext context, WidgetRef ref) {
final position = ref.watch(videoPlayerProvider(asset.id).select((s) => s.position));
return LinearProgressIndicator(
color: context.colorScheme.primary,
borderRadius: BorderRadius.zero,
minHeight: 5,
value: position.inMilliseconds / asset.duration.inMilliseconds,
);
}
}
/// Progress bar for image slides, driven by an explicit [AnimationController]. /// Progress bar for image slides, driven by an explicit [AnimationController].
/// ///
/// [TweenAnimationBuilder] creates its controller internally with the default /// [TweenAnimationBuilder] creates its controller internally with the default

View file

@ -52,6 +52,7 @@ class DriftUserSelectionPage extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final AsyncValue<List<UserDto>> suggestedShareUsers = ref.watch(driftUsersProvider); final AsyncValue<List<UserDto>> suggestedShareUsers = ref.watch(driftUsersProvider);
final sharedUsers = ref.watch(remoteAlbumSharedUsersProvider(album.id));
final sharedUsersList = useState<Set<UserDto>>({}); final sharedUsersList = useState<Set<UserDto>>({});
void addNewUsersHandler() { void addNewUsersHandler() {
@ -136,9 +137,6 @@ class DriftUserSelectionPage extends HookConsumerWidget {
), ),
body: suggestedShareUsers.widgetWhen( body: suggestedShareUsers.widgetWhen(
onData: (users) { onData: (users) {
// Get shared users for this album from the database
final sharedUsers = ref.read(remoteAlbumSharedUsersProvider(album.id));
return sharedUsers.when( return sharedUsers.when(
data: (albumSharedUsers) { data: (albumSharedUsers) {
// Filter out users that are already shared with this album and the owner // Filter out users that are already shared with this album and the owner

View file

@ -742,9 +742,7 @@ class _SearchResultGrid extends ConsumerWidget {
return false; return false;
} }
Widget? _bottomWidget(BuildContext context, WidgetRef ref) { Widget? _bottomWidget(BuildContext context, {required bool isLoading, required bool hasMore}) {
final isLoading = ref.read(paginatedSearchProvider.select((s) => s.isLoading));
if (isLoading) { if (isLoading) {
return const SliverFillRemaining( return const SliverFillRemaining(
hasScrollBody: false, hasScrollBody: false,
@ -755,8 +753,6 @@ class _SearchResultGrid extends ConsumerWidget {
); );
} }
final hasMore = ref.read(paginatedSearchProvider.select((s) => s.nextPage != null));
if (hasMore) { if (hasMore) {
return null; return null;
} }
@ -778,6 +774,7 @@ class _SearchResultGrid extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final hasAssets = ref.watch(paginatedSearchProvider.select((s) => s.assets.isNotEmpty)); final hasAssets = ref.watch(paginatedSearchProvider.select((s) => s.assets.isNotEmpty));
final isLoading = ref.watch(paginatedSearchProvider.select((s) => s.isLoading)); final isLoading = ref.watch(paginatedSearchProvider.select((s) => s.isLoading));
final hasMore = ref.watch(paginatedSearchProvider.select((s) => s.nextPage != null));
if (!hasAssets && !isLoading) { if (!hasAssets && !isLoading) {
return const _SearchNoResults(); return const _SearchNoResults();
@ -807,7 +804,7 @@ class _SearchResultGrid extends ConsumerWidget {
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20), bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
snapToMonth: false, snapToMonth: false,
loadingWidget: const SizedBox.shrink(), loadingWidget: const SizedBox.shrink(),
bottomSliverWidget: _bottomWidget(context, ref), bottomSliverWidget: _bottomWidget(context, isLoading: isLoading, hasMore: hasMore),
), ),
), ),
), ),

View file

@ -177,7 +177,15 @@ class _BackupIndicator extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final indicatorIcon = _getBackupBadgeIcon(context, ref); final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled));
final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none));
final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty));
final indicatorIcon = _getBackupBadgeIcon(
context,
backupEnabled: backupEnabled,
hasError: hasError,
isUploading: isUploading,
);
return IconButton( return IconButton(
onPressed: () => context.pushRoute(const DriftBackupRoute()), onPressed: () => context.pushRoute(const DriftBackupRoute()),
@ -192,12 +200,14 @@ class _BackupIndicator extends ConsumerWidget {
); );
} }
Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) { Widget? _getBackupBadgeIcon(
final backupEnabled = ref.read(appConfigProvider.select((c) => c.backup.enabled)); BuildContext context, {
final hasError = ref.read(driftBackupProvider.select((state) => state.error != BackupError.none)); required bool backupEnabled,
required bool hasError,
required bool isUploading,
}) {
final isDarkTheme = context.isDarkTheme; final isDarkTheme = context.isDarkTheme;
final iconColor = isDarkTheme ? Colors.white : Colors.black; final iconColor = isDarkTheme ? Colors.white : Colors.black;
final isUploading = ref.read(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty));
if (!backupEnabled) { if (!backupEnabled) {
return _BadgeLabel( return _BadgeLabel(