diff --git a/mobile/lib/domain/models/map.model.dart b/mobile/lib/domain/models/map.model.dart index 52f6fe8afd..9da424534c 100644 --- a/mobile/lib/domain/models/map.model.dart +++ b/mobile/lib/domain/models/map.model.dart @@ -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; +} diff --git a/mobile/lib/domain/models/time_range.model.dart b/mobile/lib/domain/models/time_range.model.dart index 2727a9d5c8..67ddfdc2d6 100644 --- a/mobile/lib/domain/models/time_range.model.dart +++ b/mobile/lib/domain/models/time_range.model.dart @@ -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? from, Option? to}) { return TimeRange(from: from.patch(this.from), to: to.patch(this.to)); diff --git a/mobile/lib/domain/services/map.service.dart b/mobile/lib/domain/services/map.service.dart index 5e0a3bd857..3a784e3924 100644 --- a/mobile/lib/domain/services/map.service.dart +++ b/mobile/lib/domain/services/map.service.dart @@ -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> Function(LatLngBounds? bounds); diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index 2d35ceb6c8..6bd088b74a 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -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'; diff --git a/mobile/lib/infrastructure/repositories/map.repository.dart b/mobile/lib/infrastructure/repositories/map.repository.dart index bd24985fbb..25141803a6 100644 --- a/mobile/lib/infrastructure/repositories/map.repository.dart +++ b/mobile/lib/infrastructure/repositories/map.repository.dart @@ -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() diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index 5296316421..e5dc52c2ae 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -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 with $TimelineRepositoryMixin { TimelineRepository(super.attachedDatabase); diff --git a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart index ede5a15b21..355cfdc179 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart @@ -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.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; }), ], diff --git a/mobile/lib/presentation/widgets/map/map.state.dart b/mobile/lib/presentation/widgets/map/map.state.dart index 302dce36a1..1abcf60363 100644 --- a/mobile/lib/presentation/widgets/map/map.state.dart +++ b/mobile/lib/presentation/widgets/map/map.state.dart @@ -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 { MapStateNotifier(); - final StreamController _optionsController = StreamController.broadcast(); - - // ignore: avoid-public-notifier-properties - Stream 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 { 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 { 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 { 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 { @override MapState build() { - ref.onDispose(_optionsController.close); final mapConfig = ref.read(appConfigProvider.select((config) => config.map)); return MapState( themeMode: mapConfig.themeMode,