Rewrote map options notifier to be more Riverpody

This commit is contained in:
Adam Gastineau 2026-08-20 07:50:47 -07:00
parent 758259cfd6
commit 292adc5ae2
8 changed files with 41 additions and 43 deletions

View file

@ -1,4 +1,5 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/domain/models/time_range.model.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
part 'map.model.freezed.dart';
@ -7,3 +8,17 @@ part 'map.model.freezed.dart';
abstract class Marker with _$Marker {
const factory Marker({required LatLng location, required String assetId}) = _Marker;
}
@Freezed(fromJson: false, toJson: false)
abstract class TimelineMapOptions with _$TimelineMapOptions {
const factory TimelineMapOptions({
required LatLngBounds bounds,
@Default(false) bool onlyFavorites,
@Default(false) bool includeArchived,
@Default(false) bool withPartners,
@Default(0) int relativeDays,
@Default(TimeRange()) TimeRange timeRange,
}) = _TimelineMapOptions;
}

View file

@ -1,10 +1,14 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:immich_mobile/utils/option.dart';
class TimeRange {
final DateTime? from;
final DateTime? to;
part 'time_range.model.freezed.dart';
const TimeRange({this.from, this.to});
// copyWith is hand-written so that fields can be cleared via optionals
@Freezed(copyWith: false, fromJson: false, toJson: false)
abstract class TimeRange with _$TimeRange {
const TimeRange._();
const factory TimeRange({DateTime? from, DateTime? to}) = _TimeRange;
TimeRange copyWith({Option<DateTime>? from, Option<DateTime>? to}) {
return TimeRange(from: from.patch(this.from), to: to.patch(this.to));

View file

@ -1,6 +1,5 @@
import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
typedef MapMarkerSource = Future<List<Marker>> Function(LatLngBounds? bounds);

View file

@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
import 'package:immich_mobile/constants/constants.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';

View file

@ -6,7 +6,6 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.drift.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@DriftAccessor()

View file

@ -4,7 +4,7 @@ import 'package:drift/drift.dart';
import 'package:easy_localization/easy_localization.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/time_range.model.dart';
import 'package:immich_mobile/domain/models/map.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/entities/local_asset.entity.dart';
@ -13,27 +13,8 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.drift.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:stream_transform/stream_transform.dart';
class TimelineMapOptions {
final LatLngBounds bounds;
final bool onlyFavorites;
final bool includeArchived;
final bool withPartners;
final int relativeDays;
final TimeRange timeRange;
const TimelineMapOptions({
required this.bounds,
this.onlyFavorites = false,
this.includeArchived = false,
this.withPartners = false,
this.relativeDays = 0,
this.timeRange = const TimeRange(),
});
}
@DriftAccessor()
class TimelineRepository extends DatabaseAccessor<Drift> with $TimelineRepositoryMixin {
TimelineRepository(super.attachedDatabase);

View file

@ -1,5 +1,8 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
@ -46,14 +49,21 @@ class _ScopedMapTimeline extends StatelessWidget {
final withPartners = ref.watch(mapStateProvider.select((s) => s.withPartners));
final users = withPartners ? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id] : [user.id];
final optionsController = StreamController<TimelineMapOptions>.broadcast();
ref.onDispose(optionsController.close);
var currentOptions = ref.read(mapStateProvider).toOptions();
ref.listen(mapStateProvider.select((state) => state.toOptions()), (_, newOptions) {
currentOptions = newOptions;
optionsController.add(newOptions);
});
final timelineService = ref
.watch(timelineFactoryProvider)
.geographicMap(
users,
() => ref.read(mapStateProvider).toOptions(),
ref.read(mapStateProvider.notifier).optionsStream,
);
.geographicMap(users, () => currentOptions, optionsController.stream);
ref.onDispose(timelineService.dispose);
return timelineService;
}),
],

View file

@ -3,9 +3,9 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/events.model.dart';
import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/domain/models/time_range.model.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/providers/infrastructure/map.provider.dart';
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
import 'package:immich_mobile/providers/map/map_state.provider.dart';
@ -72,17 +72,11 @@ class MapState {
class MapStateNotifier extends Notifier<MapState> {
MapStateNotifier();
final StreamController<TimelineMapOptions> _optionsController = StreamController.broadcast();
// ignore: avoid-public-notifier-properties
Stream<TimelineMapOptions> get optionsStream => _optionsController.stream;
bool setBounds(LatLngBounds bounds) {
if (state.bounds == bounds) {
return false;
}
state = state.copyWith(bounds: bounds);
_optionsController.add(state.toOptions());
return true;
}
@ -97,14 +91,12 @@ class MapStateNotifier extends Notifier<MapState> {
void switchFavoriteOnly(bool isFavoriteOnly) {
unawaited(ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly));
state = state.copyWith(onlyFavorites: isFavoriteOnly);
_optionsController.add(state.toOptions());
EventStream.shared.emit(const MapMarkerReloadEvent());
}
void switchIncludeArchived(bool isIncludeArchived) {
unawaited(ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived));
state = state.copyWith(includeArchived: isIncludeArchived);
_optionsController.add(state.toOptions());
EventStream.shared.emit(const MapMarkerReloadEvent());
}
@ -117,7 +109,6 @@ class MapStateNotifier extends Notifier<MapState> {
void setRelativeTime(int relativeDays) {
unawaited(ref.read(settingsProvider).write(.mapRelativeDate, relativeDays));
state = state.copyWith(relativeDays: relativeDays);
_optionsController.add(state.toOptions());
EventStream.shared.emit(const MapMarkerReloadEvent());
}
@ -125,7 +116,6 @@ class MapStateNotifier extends Notifier<MapState> {
unawaited(ref.read(settingsProvider).write(.mapCustomFrom, range.from));
unawaited(ref.read(settingsProvider).write(.mapCustomTo, range.to));
state = state.copyWith(timeRange: range);
_optionsController.add(state.toOptions());
EventStream.shared.emit(const MapMarkerReloadEvent());
}
@ -142,7 +132,6 @@ class MapStateNotifier extends Notifier<MapState> {
@override
MapState build() {
ref.onDispose(_optionsController.close);
final mapConfig = ref.read(appConfigProvider.select((config) => config.map));
return MapState(
themeMode: mapConfig.themeMode,