Compare commits

...

4 Commits

Author SHA1 Message Date
shenlong-tanwen
acebb36296 fix timeline not updated on orientation change 2026-04-27 01:37:01 +05:30
shenlong-tanwen
b25cffbcbc enable retries 2026-04-24 19:23:41 +05:30
shenlong-tanwen
c29dc5db16 chore: riverpod v2 to v3 2026-04-24 19:23:41 +05:30
renovate[bot]
350056dd1a fix(deps): update dependency uuid to v14 [security] (#28046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 11:24:33 +02:00
57 changed files with 454 additions and 171 deletions

View File

@@ -45,12 +45,12 @@ analyzer:
- lib/**/*.g.dart
- lib/**/*.drift.dart
# TODO: Re-enable after upgrading custom_lint
# plugins:
# - custom_lint
errors:
unawaited_futures: warning
plugins:
riverpod_lint: ^3.1.3
custom_lint:
rules:
- avoid_build_context_in_providers: false

View File

@@ -57,7 +57,11 @@ void main() async {
runApp(ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const MainWidget()));
} catch (error, stack) {
runApp(BootstrapErrorWidget(error: error.toString(), stack: stack.toString()));
runApp(
ProviderScope(
child: BootstrapErrorWidget(error: error.toString(), stack: stack.toString()),
),
);
}
}

View File

@@ -7,12 +7,12 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/images/local_album_thumbnail.widget.dart';
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
import 'package:immich_mobile/presentation/widgets/people/partner_user_avatar.widget.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/partner.provider.dart';
import 'package:immich_mobile/providers/infrastructure/people.provider.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
@@ -333,7 +333,7 @@ class _QuickAccessButtonList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final partnerSharedWithAsync = ref.watch(driftSharedWithPartnerProvider);
final partners = partnerSharedWithAsync.valueOrNull ?? [];
final partners = partnerSharedWithAsync.value ?? [];
return SliverPadding(
padding: const EdgeInsets.only(left: 16, top: 12, right: 16, bottom: 32),

View File

@@ -639,7 +639,7 @@ class DriftSearchPage extends HookConsumerWidget {
label: 'search_filter_location'.t(context: context),
currentFilter: locationCurrentFilterWidget.value,
),
if (userPreferences.valueOrNull?.tagsEnabled ?? false)
if (userPreferences.value?.tagsEnabled ?? false)
SearchFilterChip(
icon: Icons.sell_outlined,
onTap: showTagPicker,
@@ -665,7 +665,7 @@ class DriftSearchPage extends HookConsumerWidget {
label: 'search_filter_media_type'.t(context: context),
currentFilter: mediaTypeCurrentFilterWidget.value,
),
if (userPreferences.valueOrNull?.ratingsEnabled ?? false)
if (userPreferences.value?.ratingsEnabled ?? false)
SearchFilterChip(
icon: Icons.star_outline_rounded,
onTap: showStarRatingPicker,

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/search.service.dart';
import 'package:immich_mobile/models/search/search_filter.model.dart';

View File

@@ -57,6 +57,12 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
void initState() {
super.initState();
ref.listenManual(
remoteAlbumProvider.select((state) => state.albums),
(_, _) => sortAlbums(),
fireImmediately: true,
);
WidgetsBinding.instance.addPostFrameCallback((_) {
final appSettings = ref.read(appSettingsServiceProvider);
final savedSortMode = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortOrder);

View File

@@ -19,7 +19,7 @@ class AssetDetails extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final exifInfo = ref.watch(assetExifProvider(asset)).valueOrNull;
final exifInfo = ref.watch(assetExifProvider(asset)).value;
return Container(
constraints: BoxConstraints(minHeight: minHeight),

View File

@@ -363,7 +363,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
}
BaseAsset displayAsset = asset;
final stackChildren = ref.watch(stackChildrenNotifier(asset)).valueOrNull;
final stackChildren = ref.watch(stackChildrenNotifier(asset)).value;
if (stackChildren != null && stackChildren.isNotEmpty) {
displayAsset = stackChildren.elementAt(stackIndex);
}

View File

@@ -2,9 +2,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
class StackChildrenNotifier extends AutoDisposeFamilyAsyncNotifier<List<RemoteAsset>, BaseAsset> {
class StackChildrenNotifier extends AsyncNotifier<List<RemoteAsset>> {
final BaseAsset asset;
StackChildrenNotifier(this.asset);
@override
Future<List<RemoteAsset>> build(BaseAsset asset) {
Future<List<RemoteAsset>> build() {
final asset = this.asset;
if (asset is! RemoteAsset || asset.stackId == null) {
return Future.value(const []);
}

View File

@@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/misc.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/events.model.dart';
@@ -17,9 +18,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/download_statu
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_page.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_preloader.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
@@ -105,6 +106,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
final asset = ref.read(assetViewerProvider).currentAsset;
assert(asset != null, "Current asset should not be null when opening the AssetViewer");
// ignore: invalid_use_of_protected_member
if (asset != null) _stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive();
_reloadSubscription = EventStream.shared.listen(_onEvent);
@@ -161,6 +163,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
_preloader.preload(index, context.sizeData);
_handleCasting();
_stackChildrenKeepAlive?.close();
// ignore: invalid_use_of_protected_member
_stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive();
}

View File

@@ -39,7 +39,7 @@ class _ScopedMapTimeline extends StatelessWidget {
}
final users = ref.watch(mapStateProvider).withPartners
? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id]
? ref.watch(timelineUsersProvider).value ?? [user.id]
: [user.id];
final timelineService = ref

View File

@@ -1,5 +1,6 @@
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
@@ -48,6 +49,26 @@ class TimelineArgs {
showStorageIndicator.hashCode ^
withStack.hashCode ^
groupBy.hashCode;
TimelineArgs copyWith({
double? maxWidth,
double? maxHeight,
double? spacing,
int? columnCount,
bool? showStorageIndicator,
bool? withStack,
GroupAssetsBy? groupBy,
}) {
return TimelineArgs(
maxWidth: maxWidth ?? this.maxWidth,
maxHeight: maxHeight ?? this.maxHeight,
spacing: spacing ?? this.spacing,
columnCount: columnCount ?? this.columnCount,
showStorageIndicator: showStorageIndicator ?? this.showStorageIndicator,
withStack: withStack ?? this.withStack,
groupBy: groupBy ?? this.groupBy,
);
}
}
class TimelineState {
@@ -86,25 +107,37 @@ class TimelineStateNotifier extends Notifier<TimelineState> {
// This provider watches the buckets from the timeline service & args and serves the segments.
// It should be used only after the timeline service and timeline args provider is overridden
final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>((ref) async* {
final args = ref.watch(timelineArgsProvider);
final columnCount = args.columnCount;
final spacing = args.spacing;
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
final tileExtent = math.max(0, availableTileWidth) / columnCount;
final timelineSegmentProvider = StreamNotifierProvider<_TimelineSegmentNotifier, List<Segment>>(
_TimelineSegmentNotifier.new,
dependencies: [timelineServiceProvider, timelineArgsProvider],
);
final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
class _TimelineSegmentNotifier extends StreamNotifier<List<Segment>> {
@override
Stream<List<Segment>> build() async* {
final args = ref.watch(timelineArgsProvider);
final columnCount = args.columnCount;
final spacing = args.spacing;
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
final tileExtent = math.max(0, availableTileWidth) / columnCount;
final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
final timelineService = ref.watch(timelineServiceProvider);
yield* timelineService.watchBuckets().map((buckets) {
return FixedSegmentBuilder(
buckets: buckets,
tileHeight: tileExtent,
columnCount: columnCount,
spacing: spacing,
groupBy: groupBy,
).generate();
});
}
final timelineService = ref.watch(timelineServiceProvider);
yield* timelineService.watchBuckets().map((buckets) {
return FixedSegmentBuilder(
buckets: buckets,
tileHeight: tileExtent,
columnCount: columnCount,
spacing: spacing,
groupBy: groupBy,
).generate();
});
}, dependencies: [timelineServiceProvider, timelineArgsProvider]);
@override
bool updateShouldNotify(AsyncValue<List<Segment>> previous, AsyncValue<List<Segment>> next) {
final listEquals = const DeepCollectionEquality().equals;
return !listEquals(previous.value, next.value);
}
}
final timelineStateProvider = NotifierProvider<TimelineStateNotifier, TimelineState>(TimelineStateNotifier.new);

View File

@@ -71,10 +71,9 @@ class Timeline extends StatelessWidget {
builder: (_, constraints) => ProviderScope(
overrides: [
timelineArgsProvider.overrideWith(
(ref) => TimelineArgs(
maxWidth: constraints.maxWidth,
maxHeight: constraints.maxHeight,
columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))),
() => TimelineArgsNotifier(
initialMaxWidth: constraints.maxWidth,
initialMaxHeight: constraints.maxHeight,
showStorageIndicator: showStorageIndicator,
withStack: withStack,
groupBy: groupBy,
@@ -92,6 +91,7 @@ class Timeline extends StatelessWidget {
persistentBottomBar: persistentBottomBar,
snapToMonth: snapToMonth,
maxWidth: constraints.maxWidth,
maxHeight: constraints.maxHeight,
loadingWidget: loadingWidget,
),
),
@@ -122,6 +122,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
this.persistentBottomBar = false,
this.snapToMonth = true,
this.maxWidth,
this.maxHeight,
this.loadingWidget,
});
@@ -134,6 +135,7 @@ class _SliverTimeline extends ConsumerStatefulWidget {
final bool persistentBottomBar;
final bool snapToMonth;
final double? maxWidth;
final double? maxHeight;
final Widget? loadingWidget;
@override
@@ -172,13 +174,21 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
@override
void didUpdateWidget(covariant _SliverTimeline oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.maxWidth != oldWidget.maxWidth) {
final asyncSegments = ref.read(timelineSegmentProvider);
asyncSegments.whenData((segments) {
final index = _getCurrentAssetIndex(segments);
// Refresh to wait for new segments to be generated with the updated width before restoring the scroll position
final _ = ref.refresh(timelineArgsProvider);
_restoreAssetIndex = index;
if (widget.maxWidth != oldWidget.maxWidth || widget.maxHeight != oldWidget.maxHeight) {
final segments = ref.read(timelineSegmentProvider).value;
int? restoreAssetIndex;
if (segments != null) {
restoreAssetIndex = _getCurrentAssetIndex(segments);
}
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
ref
.read(timelineArgsProvider.notifier)
.updateConstraints(maxWidth: widget.maxWidth!, maxHeight: widget.maxHeight!);
final _ = ref.refresh(timelineSegmentProvider);
_restoreAssetIndex = restoreAssetIndex;
_restoreAssetPosition(null);
}
});
}
}
@@ -200,26 +210,40 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
}
}
void _restorePosition(List<Segment> segments) {
final targetSegment = segments.lastWhereOrNull((segment) => segment.firstAssetIndex <= _restoreAssetIndex!);
if (targetSegment == null) {
_restoreAssetIndex = null;
return;
}
final assetIndexInSegment = _restoreAssetIndex! - targetSegment.firstAssetIndex;
final newColumnCount = ref.read(timelineArgsProvider).columnCount;
final rowIndexInSegment = (assetIndexInSegment / newColumnCount).floor();
final targetRowIndex = targetSegment.firstIndex + 1 + rowIndexInSegment;
final targetOffset = targetSegment.indexToLayoutOffset(targetRowIndex);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && _scrollController.hasClients) {
_scrollController.jumpTo(targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent));
}
});
_restoreAssetIndex = null;
}
void _restoreAssetPosition(_) {
if (_restoreAssetIndex == null) return;
final asyncSegments = ref.read(timelineSegmentProvider);
asyncSegments.whenData((segments) {
final targetSegment = segments.lastWhereOrNull((segment) => segment.firstAssetIndex <= _restoreAssetIndex!);
if (targetSegment != null) {
final assetIndexInSegment = _restoreAssetIndex! - targetSegment.firstAssetIndex;
final newColumnCount = ref.read(timelineArgsProvider).columnCount;
final rowIndexInSegment = (assetIndexInSegment / newColumnCount).floor();
final targetRowIndex = targetSegment.firstIndex + 1 + rowIndexInSegment;
final targetOffset = targetSegment.indexToLayoutOffset(targetRowIndex);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_scrollController.jumpTo(targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent));
}
});
if (asyncSegments is AsyncData<List<Segment>>) {
_restorePosition(asyncSegments.value);
return;
}
late ProviderSubscription<AsyncValue<List<Segment>>> sub;
sub = ref.listenManual<AsyncValue<List<Segment>>>(timelineSegmentProvider, (_, next) {
if (next is AsyncData<List<Segment>>) {
sub.close();
_restorePosition(next.value);
}
});
_restoreAssetIndex = null;
}
void _onMultiSelectionToggled(_, bool isEnabled) {

View File

@@ -3,20 +3,19 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/models/activities/activity.model.dart';
import 'package:immich_mobile/providers/activity_service.provider.dart';
// ignore: unintended_html_in_doc_comment
/// Maintains the current list of all activities for <share-album-id, asset>
/// Maintains the current list of all activities for [share-album-id, asset]
final albumActivityProvider = AsyncNotifierProvider.autoDispose
.family<AlbumActivity, List<Activity>, (String albumId, String? assetId)>(AlbumActivity.new);
.family<AlbumActivity, List<Activity>, (String, String?)>(AlbumActivity.new);
class AlbumActivity extends AutoDisposeFamilyAsyncNotifier<List<Activity>, (String albumId, String? assetId)> {
late String albumId;
late String? assetId;
class AlbumActivity extends AsyncNotifier<List<Activity>> {
final String albumId;
final String? assetId;
AlbumActivity((String albumId, String? assetId) args) : albumId = args.$1, assetId = args.$2;
@override
Future<List<Activity>> build((String albumId, String? assetId) args) async {
albumId = args.$1;
assetId = args.$2;
Future<List<Activity>> build() async {
return ref.watch(activityServiceProvider).getAllActivities(albumId, assetId: assetId);
}
@@ -57,7 +56,7 @@ class AlbumActivity extends AutoDisposeFamilyAsyncNotifier<List<Activity>, (Stri
}
void _addToState(Activity activity) {
final activities = state.valueOrNull ?? [];
final activities = state.value ?? [];
if (activities.any((a) => a.id == activity.id)) {
return;
}
@@ -65,7 +64,7 @@ class AlbumActivity extends AutoDisposeFamilyAsyncNotifier<List<Activity>, (Stri
}
Activity? _removeFromState(String id) {
final activities = state.valueOrNull;
final activities = state.value;
if (activities == null) {
return null;
}

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
class AlbumTitleNotifier extends StateNotifier<String> {
AlbumTitleNotifier() : super("");

View File

@@ -1,26 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'current_album.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$currentAlbumHash() => r'61f00273d6b69da45add1532cc3d3a076ee55110';
/// See also [CurrentAlbum].
@ProviderFor(CurrentAlbum)
final currentAlbumProvider =
AutoDisposeNotifierProvider<CurrentAlbum, Album?>.internal(
CurrentAlbum.new,
name: r'currentAlbumProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$currentAlbumHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$CurrentAlbum = AutoDisposeNotifier<Album?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
@@ -67,24 +69,41 @@ class AssetViewerState {
}
class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
StreamSubscription<BaseAsset?>? _assetSub;
String? _watchedHeroTag;
@override
AssetViewerState build() {
ref.listen(_watchedCurrentAssetProvider, (_, next) {
final updated = next.valueOrNull;
if (updated != null) {
state = state.copyWith(currentAsset: updated);
}
ref.onDispose(() {
_assetSub?.cancel();
_assetSub = null;
_watchedHeroTag = null;
});
return const AssetViewerState();
}
void _syncAssetSubscription(BaseAsset? asset) {
final heroTag = asset?.heroTag;
if (heroTag == _watchedHeroTag) return;
_watchedHeroTag = heroTag;
_assetSub?.cancel();
_assetSub = null;
if (asset == null) return;
_assetSub = ref.read(assetServiceProvider).watchAsset(asset).listen((updated) {
if (updated == null || updated.heroTag != _watchedHeroTag) return;
state = state.copyWith(currentAsset: updated);
});
}
void reset() {
state = const AssetViewerState();
_syncAssetSubscription(null);
}
void setAsset(BaseAsset asset) {
if (asset == state.currentAsset) return;
state = state.copyWith(currentAsset: asset, stackIndex: 0);
_syncAssetSubscription(asset);
}
void setOpacity(double opacity) {
@@ -134,10 +153,3 @@ class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
}
final assetViewerProvider = NotifierProvider<AssetViewerStateNotifier, AssetViewerState>(AssetViewerStateNotifier.new);
final _watchedCurrentAssetProvider = StreamProvider<BaseAsset?>((ref) {
ref.watch(assetViewerProvider.select((s) => s.currentAsset?.heroTag));
final asset = ref.read(assetViewerProvider).currentAsset;
if (asset == null) return const Stream.empty();
return ref.read(assetServiceProvider).watchAsset(asset);
});

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:background_downloader/background_downloader.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/models/download/download_state.model.dart';
import 'package:immich_mobile/models/download/livephotos_medatada.model.dart';
import 'package:immich_mobile/services/download.service.dart';

View File

@@ -1,4 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
/// Whether to display the video part of a motion photo
final isPlayingMotionVideoProvider = StateNotifierProvider<IsPlayingMotionVideo, bool>((ref) {

View File

@@ -1,10 +1,10 @@
import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/share_intent_service.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:immich_mobile/services/share_intent_service.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;

View File

@@ -1,4 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
final showControlsProvider = StateNotifierProvider<ShowControls, bool>((ref) {
return ShowControls(ref);

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:logging/logging.dart';
import 'package:native_video_player/native_video_player.dart';
import 'package:wakelock_plus/wakelock_plus.dart';

View File

@@ -1,5 +1,6 @@
import 'package:flutter_udid/flutter_udid.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
@@ -11,9 +12,9 @@ import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/auth.service.dart';
import 'package:immich_mobile/services/background_upload.service.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:immich_mobile/services/secure_storage.service.dart';
import 'package:immich_mobile/services/background_upload.service.dart';
import 'package:immich_mobile/services/widget.service.dart';
import 'package:immich_mobile/utils/debug_print.dart';
import 'package:immich_mobile/utils/hash.dart';

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
/// Tracks per-asset upload progress.
/// Key: local asset ID, Value: upload progress 0.0 to 1.0, or -1.0 for error

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';
import 'package:immich_mobile/services/server_info.service.dart';

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/services/local_album.service.dart';
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';

View File

@@ -2,16 +2,16 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/utils/upload_speed_calculator.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:immich_mobile/services/background_upload.service.dart';
import 'package:immich_mobile/services/foreground_upload.service.dart';
import 'package:immich_mobile/utils/upload_speed_calculator.dart';
import 'package:logging/logging.dart';
class EnqueueStatus {
final int enqueueCount;

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/services/gcast.service.dart';

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';

View File

@@ -1,4 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/models/folder/root_folder.model.dart';

View File

@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:permission_handler/permission_handler.dart';
class GalleryPermissionNotifier extends StateNotifier<PermissionStatus> {

View File

@@ -1,5 +1,6 @@
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';

View File

@@ -16,7 +16,7 @@ final mapServiceProvider = Provider<MapService>(
}
final users = ref.watch(mapStateProvider).withPartners
? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id]
? ref.watch(timelineUsersProvider).value ?? [user.id]
: [user.id];
final mapService = ref.watch(mapFactoryProvider).remote(users, ref.watch(mapStateProvider).toOptions());

View File

@@ -1,4 +1,7 @@
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/setting.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
@@ -10,13 +13,51 @@ final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
(ref) => DriftTimelineRepository(ref.watch(driftProvider)),
);
final timelineArgsProvider = Provider.autoDispose<TimelineArgs>(
(ref) => throw UnimplementedError('Will be overridden through a ProviderScope.'),
final timelineArgsProvider = NotifierProvider.autoDispose<TimelineArgsNotifier, TimelineArgs>(
TimelineArgsNotifier.new,
dependencies: const [],
);
class TimelineArgsNotifier extends Notifier<TimelineArgs> {
TimelineArgsNotifier({
double initialMaxWidth = 0,
double initialMaxHeight = 0,
this.showStorageIndicator = false,
this.withStack = false,
this.groupBy,
}) : _maxWidth = initialMaxWidth,
_maxHeight = initialMaxHeight;
double _maxWidth;
double _maxHeight;
final bool showStorageIndicator;
final bool withStack;
final GroupAssetsBy? groupBy;
@override
TimelineArgs build() {
final columnCount = ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow)));
return TimelineArgs(
maxWidth: _maxWidth,
maxHeight: _maxHeight,
columnCount: columnCount,
showStorageIndicator: showStorageIndicator,
withStack: withStack,
groupBy: groupBy,
);
}
void updateConstraints({required double maxWidth, required double maxHeight}) {
if (_maxWidth == maxWidth && _maxHeight == maxHeight) return;
_maxWidth = maxWidth;
_maxHeight = maxHeight;
state = state.copyWith(maxWidth: maxWidth, maxHeight: maxHeight);
}
}
final timelineServiceProvider = Provider<TimelineService>(
(ref) {
final timelineUsers = ref.watch(timelineUsersProvider).valueOrNull ?? [];
final timelineUsers = ref.watch(timelineUsersProvider).value ?? [];
final timelineService = ref.watch(timelineFactoryProvider).main(timelineUsers);
ref.onDispose(timelineService.dispose);
return timelineService;
@@ -33,11 +74,22 @@ final timelineFactoryProvider = Provider<TimelineFactory>(
),
);
final timelineUsersProvider = StreamProvider<List<String>>((ref) {
final currentUserId = ref.watch(currentUserProvider.select((u) => u?.id));
if (currentUserId == null) {
return Stream.value([]);
final timelineUsersProvider = StreamNotifierProvider<_TimelineUsersNotifier, List<String>>(_TimelineUsersNotifier.new);
class _TimelineUsersNotifier extends StreamNotifier<List<String>> {
@override
Stream<List<String>> build() {
final currentUserId = ref.watch(currentUserProvider.select((u) => u?.id));
if (currentUserId == null) {
return Stream.value([]);
}
return ref.watch(timelineRepositoryProvider).watchTimelineUserIds(currentUserId);
}
return ref.watch(timelineRepositoryProvider).watchTimelineUserIds(currentUserId);
});
@override
bool updateShouldNotify(AsyncValue<List<String>> previous, AsyncValue<List<String>> next) {
final listEquals = const DeepCollectionEquality().equals;
return !listEquals(previous.value, next.value);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/models/auth/biometric_status.model.dart';

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
final multiselectProvider = StateProvider((ref) {
return false;

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/services/network.service.dart';
final networkProvider = StateNotifierProvider<NetworkNotifier, String>((ref) {

View File

@@ -1,6 +1,6 @@
import 'dart:io';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:permission_handler/permission_handler.dart';
class NotificationPermissionNotifier extends StateNotifier<PermissionStatus> {

View File

@@ -1,5 +1,5 @@
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
final inLockedViewProvider = StateProvider<bool>((ref) => false);
final currentRouteNameProvider = StateProvider<String?>((ref) => null);

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
final secureStorageProvider = StateNotifierProvider<SecureStorageProvider, void>((ref) {
return SecureStorageProvider();

View File

@@ -1,4 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/models/server_info/server_config.model.dart';
import 'package:immich_mobile/models/server_info/server_disk_info.model.dart';

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
import 'package:immich_mobile/services/shared_link.service.dart';

View File

@@ -1,4 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
enum TabEnum { home, search, albums, library }

View File

@@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/theme/color_scheme.dart';
import 'package:immich_mobile/theme/theme_data.dart';
import 'package:immich_mobile/theme/dynamic_theme.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/theme/color_scheme.dart';
import 'package:immich_mobile/theme/dynamic_theme.dart';
import 'package:immich_mobile/theme/theme_data.dart';
import 'package:immich_mobile/utils/debug_print.dart';
final immichThemeModeProvider = StateProvider<ThemeMode>((ref) {

View File

@@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';

View File

@@ -25,6 +25,7 @@ final deepLinkServiceProvider = Provider(
ref.watch(driftPeopleServiceProvider),
ref.watch(currentUserProvider),
),
dependencies: [remoteAlbumServiceProvider],
);
class DeepLinkService {

View File

@@ -3,12 +3,13 @@ import 'dart:math';
import 'package:async/async.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/legacy.dart';
import 'package:immich_mobile/constants/colors.dart';
import 'package:immich_mobile/extensions/duration_extensions.dart';
import 'package:immich_mobile/models/cast/cast_manager_state.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/extensions/duration_extensions.dart';
import 'package:immich_mobile/widgets/asset_viewer/animated_play_pause.dart';
class VideoControls extends ConsumerStatefulWidget {
@@ -25,7 +26,7 @@ class VideoControls extends ConsumerStatefulWidget {
class _VideoControlsState extends ConsumerState<VideoControls> {
late final RestartableTimer _hideTimer;
AutoDisposeStateNotifierProvider<VideoPlayerNotifier, VideoPlayerState> get _provider =>
StateNotifierProvider<VideoPlayerNotifier, VideoPlayerState> get _provider =>
videoPlayerProvider(widget.videoPlayerName);
@override

View File

@@ -5,10 +5,10 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/theme.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class ThemeSetting extends HookConsumerWidget {
const ThemeSetting({super.key});
@@ -21,7 +21,8 @@ class ThemeSetting extends HookConsumerWidget {
final isSystemTheme = useValueNotifier(currentTheme.value == ThemeMode.system);
final applyThemeToBackgroundSetting = useAppSettingsState(AppSettingsEnum.colorfulInterface);
final applyThemeToBackgroundProvider = useValueNotifier(ref.read(colorfulInterfaceSettingProvider));
final isColorfulInterface = ref.read(colorfulInterfaceSettingProvider);
final applyThemeToBackgroundProvider = useValueNotifier(isColorfulInterface);
useValueChanged(
currentThemeString.value,

View File

@@ -9,6 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "93.0.0"
analysis_server_plugin:
dependency: transitive
description:
name: analysis_server_plugin
sha256: "5f3920acbd5765764ec9ef6c5bbdd102015424281232ee4fb4f5431c87abb4eb"
url: "https://pub.dev"
source: hosted
version: "0.3.7"
analyzer:
dependency: transitive
description:
@@ -17,6 +25,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "10.0.1"
analyzer_buffer:
dependency: transitive
description:
name: analyzer_buffer
sha256: "5fcd06b0715ebeee99f03e3f437b3412249969d8d12b191ea8a1d76e42a4e4a1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
analyzer_plugin:
dependency: transitive
description:
name: analyzer_plugin
sha256: "7df504f0c9d6891bacc9f73a5a8c5f6fe4fc49c90ec8e3379916372906ba0b32"
url: "https://pub.dev"
source: hosted
version: "0.14.1"
ansicolor:
dependency: transitive
description:
@@ -209,6 +233,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.4"
cli_config:
dependency: transitive
description:
name: cli_config
sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
url: "https://pub.dev"
source: hosted
version: "0.2.0"
cli_util:
dependency: transitive
description:
@@ -273,6 +305,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.2"
coverage:
dependency: transitive
description:
name: coverage
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
url: "https://pub.dev"
source: hosted
version: "1.15.0"
crop_image:
dependency: "direct main"
description:
@@ -557,10 +597,10 @@ packages:
dependency: transitive
description:
name: flutter_riverpod
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
sha256: "4e166be88e1dbbaa34a280bdb744aeae73b7ef25fdf8db7a3bb776760a3648e2"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "3.3.1"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -659,6 +699,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.2.14"
freezed_annotation:
dependency: transitive
description:
name: freezed_annotation
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
frontend_server_client:
dependency: transitive
description:
@@ -780,10 +828,10 @@ packages:
dependency: "direct main"
description:
name: hooks_riverpod
sha256: "70bba33cfc5670c84b796e6929c54b8bc5be7d0fe15bb28c2560500b9ad06966"
sha256: "08527ec06aaef75e4b78694e045ef0cd8346594eaf9cc18b0f866398b07b93b1"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "3.3.1"
hotreloader:
dependency: transitive
description:
@@ -1149,6 +1197,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
objective_c:
dependency: transitive
description:
@@ -1433,10 +1489,28 @@ packages:
dependency: transitive
description:
name: riverpod
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
sha256: "8c22216be8ad3ef2b44af3a329693558c98eca7b8bd4ef495c92db0bba279f83"
url: "https://pub.dev"
source: hosted
version: "2.6.1"
version: "3.2.1"
riverpod_analyzer_utils:
dependency: "direct overridden"
description:
path: "packages/riverpod_analyzer_utils"
ref: e8b84952e40b395ef47ab1f581eddddf64f0b2fd
resolved-ref: e8b84952e40b395ef47ab1f581eddddf64f0b2fd
url: "https://github.com/rrousselGit/riverpod/"
source: git
version: "1.0.0-dev.9"
riverpod_lint:
dependency: "direct dev"
description:
path: "packages/riverpod_lint"
ref: e8b84952e40b395ef47ab1f581eddddf64f0b2fd
resolved-ref: e8b84952e40b395ef47ab1f581eddddf64f0b2fd
url: "https://github.com/rrousselGit/riverpod/"
source: git
version: "3.1.3"
scroll_date_picker:
dependency: "direct main"
description:
@@ -1565,6 +1639,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.2"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
url: "https://pub.dev"
source: hosted
version: "1.1.3"
shelf_web_socket:
dependency: transitive
description:
@@ -1611,6 +1701,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.2"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
url: "https://pub.dev"
source: hosted
version: "2.1.2"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
url: "https://pub.dev"
source: hosted
version: "0.10.13"
source_span:
dependency: transitive
description:
@@ -1707,6 +1813,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test:
dependency: transitive
description:
name: test
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
url: "https://pub.dev"
source: hosted
version: "1.30.0"
test_api:
dependency: transitive
description:
@@ -1715,6 +1829,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.10"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
url: "https://pub.dev"
source: hosted
version: "0.6.16"
thumbhash:
dependency: "direct main"
description:
@@ -1923,6 +2045,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
win32:
dependency: transitive
description:
@@ -1987,6 +2117,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.3"
yaml_edit:
dependency: transitive
description:
name: yaml_edit
sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
sdks:
dart: ">=3.11.0 <4.0.0"
flutter: "3.41.7"

View File

@@ -35,7 +35,7 @@ dependencies:
fluttertoast: ^8.2.14
geolocator: ^14.0.2
home_widget: ^0.8.1
hooks_riverpod: ^2.6.1
hooks_riverpod: ^3.3.1
http: ^1.6.0
image_picker: ^1.2.1
immich_ui:
@@ -91,10 +91,9 @@ dependencies:
dev_dependencies:
auto_route_generator: ^10.5.0
build_runner: ^2.13.1
# Drift generator
drift_dev: ^2.32.1
fake_async: ^1.3.3
file: ^7.0.1 # for MemoryFileSystem
file: ^7.0.1
flutter_launcher_icons: ^0.14.4
flutter_lints: ^5.0.0
flutter_native_splash: ^2.4.7
@@ -103,13 +102,27 @@ dev_dependencies:
integration_test:
sdk: flutter
mocktail: ^1.0.5
# Type safe platform code
pigeon: ^26.3.4
riverpod_lint: ^3.1.3
# cast 2.1.0 declares a loose bonsoir range but its code targets the 5.x API.
# Pin bonsoir to 5.x until cast releases a version compatible with bonsoir 6.x.
dependency_overrides:
# cast 2.1.0 declares a loose bonsoir range but its code targets the 5.x API.
# Pin bonsoir to 5.x until cast releases a version compatible with bonsoir 6.x.
bonsoir: ^5.1.11
# the pub version has an outdated analyzer dependency, and the git version is not published to pub.dev
# use the git version until the pub version is updated
riverpod_lint:
git:
url: https://github.com/rrousselGit/riverpod/
ref: 'e8b84952e40b395ef47ab1f581eddddf64f0b2fd'
path: packages/riverpod_lint/
# transitive dependency of riverpod_lint, and the pub version is outdated
# remove the override when the pub version of riverpod_lint is updated
riverpod_analyzer_utils:
git:
url: https://github.com/rrousselGit/riverpod/
ref: 'e8b84952e40b395ef47ab1f581eddddf64f0b2fd'
path: packages/riverpod_analyzer_utils/
flutter:
uses-material-design: true

View File

@@ -7,6 +7,7 @@ import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/misc.dart';
import 'package:immich_mobile/domain/services/store.service.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
@@ -54,7 +55,7 @@ void main() {
mapStateNotifier.state = mapState.copyWith(darkStyleFetched: const AsyncData("dark"));
await tester.pumpAndSettle();
expect(mapStyle?.valueOrNull, "dark");
expect(mapStyle?.value, "dark");
});
testWidgets("Return error when style is not fetched", (tester) async {
@@ -88,7 +89,7 @@ void main() {
mapStateNotifier.state = mapState.copyWith(themeMode: ThemeMode.light, lightStyleFetched: const AsyncData("light"));
await tester.pumpAndSettle();
expect(mapStyle?.valueOrNull, "light");
expect(mapStyle?.value, "light");
});
group("System mode", () {
@@ -111,7 +112,7 @@ void main() {
);
await tester.pumpAndSettle();
expect(mapStyle?.valueOrNull, "dark");
expect(mapStyle?.value, "dark");
});
testWidgets("Return light theme style when system is light", (tester) async {
@@ -133,7 +134,7 @@ void main() {
);
await tester.pumpAndSettle();
expect(mapStyle?.valueOrNull, "light");
expect(mapStyle?.value, "light");
});
testWidgets("Switches style when system brightness changes", (tester) async {
@@ -155,11 +156,11 @@ void main() {
darkStyleFetched: const AsyncData("dark"),
);
await tester.pumpAndSettle();
expect(mapStyle?.valueOrNull, "light");
expect(mapStyle?.value, "light");
tester.binding.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
await tester.pumpAndSettle();
expect(mapStyle?.valueOrNull, "dark");
expect(mapStyle?.value, "dark");
});
});
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:hooks_riverpod/misc.dart';
extension PumpConsumerWidget on WidgetTester {
/// Wraps the provided [widget] with Material app such that it becomes:

10
pnpm-lock.yaml generated
View File

@@ -570,8 +570,8 @@ importers:
specifier: ^2.0.0
version: 2.0.9
uuid:
specifier: ^11.1.0
version: 11.1.0
specifier: ^14.0.0
version: 14.0.0
validator:
specifier: ^13.12.0
version: 13.15.35
@@ -12110,6 +12110,10 @@ packages:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
uuid@14.0.0:
resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==}
hasBin: true
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -25779,6 +25783,8 @@ snapshots:
uuid@11.1.0: {}
uuid@14.0.0: {}
uuid@8.3.2: {}
validator@13.15.35: {}

View File

@@ -114,7 +114,7 @@
"thumbhash": "^0.1.1",
"transformation-matrix": "^3.1.0",
"ua-parser-js": "^2.0.0",
"uuid": "^11.1.0",
"uuid": "^14.0.0",
"validator": "^13.12.0",
"zod": "^4.3.6"
},