refactor: map query

This commit is contained in:
wuzihao051119 2025-07-16 17:28:01 +08:00 committed by mertalev
parent 48f0e2d898
commit fca27e1cee
No known key found for this signature in database
GPG Key ID: DF6ABC77AAD98C95
5 changed files with 78 additions and 51 deletions

View File

@ -2,7 +2,11 @@ import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:maplibre_gl/maplibre_gl.dart';
typedef MapMarkerSource = Stream<List<Marker>> Function(LatLngBounds bounds); typedef MapMarkerSource = Stream<List<Marker>> Function(LatLngBounds? bounds);
typedef MapQuery = ({
MapMarkerSource markerSource,
});
class MapFactory { class MapFactory {
final DriftMapRepository _mapRepository; final DriftMapRepository _mapRepository;
@ -11,25 +15,29 @@ class MapFactory {
required DriftMapRepository mapRepository, required DriftMapRepository mapRepository,
}) : _mapRepository = mapRepository; }) : _mapRepository = mapRepository;
MapService main(List<String> timelineUsers) => MapService( MapService remote(String ownerId) =>
markerSource: (bounds) => MapService(_mapRepository.remote(ownerId));
_mapRepository.watchMainMarker(timelineUsers, bounds: bounds),
);
MapService remoteAlbum({required String albumId}) => MapService( MapService favorite(String ownerId) =>
markerSource: (bounds) => MapService(_mapRepository.favorite(ownerId));
_mapRepository.watchRemoteAlbumMarker(albumId, bounds: bounds),
); MapService locked(String ownerId) =>
MapService(_mapRepository.locked(ownerId));
} }
class MapService { class MapService {
final MapMarkerSource _markerSource; final MapMarkerSource _markerSource;
const MapService({ MapService(MapQuery query)
: this._(
markerSource: query.markerSource,
);
MapService._({
required MapMarkerSource markerSource, required MapMarkerSource markerSource,
}) : _markerSource = markerSource; }) : _markerSource = markerSource;
Stream<List<Marker>> Function(LatLngBounds bounds) get watchMarkers => Stream<List<Marker>> Function(LatLngBounds? bounds) get watchMarkers =>
_markerSource; _markerSource;
Future<void> dispose() async {} Future<void> dispose() async {}

View File

@ -1,7 +1,9 @@
import 'package:drift/drift.dart'; import 'package:drift/drift.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/map.model.dart'; import 'package:immich_mobile/domain/models/map.model.dart';
import 'package:immich_mobile/domain/services/map.service.dart';
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; 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/db.repository.dart';
import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:maplibre_gl/maplibre_gl.dart';
import 'package:stream_transform/stream_transform.dart'; import 'package:stream_transform/stream_transform.dart';
@ -11,36 +13,44 @@ class DriftMapRepository extends DriftDatabaseRepository {
const DriftMapRepository(super._db) : _db = _db; const DriftMapRepository(super._db) : _db = _db;
Stream<List<Marker>> watchMainMarker( MapQuery remote(String ownerId) => _mapQueryBuilder(
List<String> userIds, { assetFilter: (row) =>
required LatLngBounds bounds, row.deletedAt.isNull() &
}) { row.visibility.equalsValue(AssetVisibility.timeline) &
final query = _db.remoteExifEntity.select().join([ row.ownerId.equals(ownerId),
innerJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteExifEntity.assetId),
useColumns: false,
),
])
..where(
_db.remoteExifEntity.latitude.isNotNull() &
_db.remoteExifEntity.longitude.isNotNull() &
_db.remoteExifEntity.inBounds(bounds) &
_db.remoteAssetEntity.visibility
.equalsValue(AssetVisibility.timeline) &
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.ownerId.isIn(userIds),
); );
return query MapQuery favorite(String ownerId) => _mapQueryBuilder(
.map((row) => row.readTable(_db.remoteExifEntity).toMarker()) assetFilter: (row) =>
.watch() row.deletedAt.isNull() &
.throttle(const Duration(seconds: 3)); row.isFavorite.equals(true) &
row.ownerId.equals(ownerId),
);
MapQuery locked(String userId) => _mapQueryBuilder(
assetFilter: (row) =>
row.deletedAt.isNull() &
row.visibility.equalsValue(AssetVisibility.locked) &
row.ownerId.equals(userId),
);
MapQuery _mapQueryBuilder({
Expression<bool> Function($RemoteAssetEntityTable row)? assetFilter,
Expression<bool> Function($RemoteExifEntityTable row)? exifFilter,
}) {
return (
markerSource: (bounds) => _watchMapMarker(
assetFilter: assetFilter,
exifFilter: exifFilter,
bounds: bounds,
)
);
} }
Stream<List<Marker>> watchRemoteAlbumMarker( Stream<List<Marker>> _watchMapMarker({
String albumId, { Expression<bool> Function($RemoteAssetEntityTable row)? assetFilter,
required LatLngBounds bounds, Expression<bool> Function($RemoteExifEntityTable row)? exifFilter,
LatLngBounds? bounds,
}) { }) {
final query = _db.remoteExifEntity.select().join([ final query = _db.remoteExifEntity.select().join([
innerJoin( innerJoin(
@ -48,20 +58,24 @@ class DriftMapRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id.equalsExp(_db.remoteExifEntity.assetId), _db.remoteAssetEntity.id.equalsExp(_db.remoteExifEntity.assetId),
useColumns: false, useColumns: false,
), ),
leftOuterJoin(
_db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
useColumns: false,
),
]) ])
..where( ..where(
_db.remoteExifEntity.latitude.isNotNull() & _db.remoteExifEntity.latitude.isNotNull() &
_db.remoteExifEntity.longitude.isNotNull() & _db.remoteExifEntity.longitude.isNotNull(),
_db.remoteExifEntity.inBounds(bounds) &
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAlbumAssetEntity.albumId.equals(albumId),
); );
if (assetFilter != null) {
query.where(assetFilter(_db.remoteAssetEntity));
}
if (exifFilter != null) {
query.where(exifFilter(_db.remoteExifEntity));
}
if (bounds != null) {
query.where(_db.remoteExifEntity.inBounds(bounds));
}
return query return query
.map((row) => row.readTable(_db.remoteExifEntity).toMarker()) .map((row) => row.readTable(_db.remoteExifEntity).toMarker())
.watch() .watch()

View File

@ -19,7 +19,7 @@ class MapBottomSheet extends ConsumerWidget {
return ProviderScope( return ProviderScope(
overrides: [ overrides: [
// TODO: refactor (timeline): when ProviderScope changed, we should refresh timeline // TODO: when ProviderScope changed, we should refresh timeline
timelineServiceProvider.overrideWith((ref) { timelineServiceProvider.overrideWith((ref) {
final timelineService = final timelineService =
ref.watch(timelineFactoryProvider).map(bounds); ref.watch(timelineFactoryProvider).map(bounds);

View File

@ -74,7 +74,8 @@ class _DriftMapWithMarkerState extends ConsumerState<DriftMapWithMarker> {
} }
await mapController!.addSource( await mapController!.addSource(
MapUtils.defaultSourceId, GeojsonSourceProperties(data: markers), MapUtils.defaultSourceId,
GeojsonSourceProperties(data: markers),
); );
if (Platform.isAndroid) { if (Platform.isAndroid) {

View File

@ -2,7 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/infrastructure/repositories/map.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/map.repository.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/domain/services/map.service.dart'; import 'package:immich_mobile/domain/services/map.service.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
final mapRepositoryProvider = Provider<DriftMapRepository>( final mapRepositoryProvider = Provider<DriftMapRepository>(
(ref) => DriftMapRepository(ref.watch(driftProvider)), (ref) => DriftMapRepository(ref.watch(driftProvider)),
@ -10,8 +10,12 @@ final mapRepositoryProvider = Provider<DriftMapRepository>(
final mapServiceProvider = Provider<MapService>( final mapServiceProvider = Provider<MapService>(
(ref) { (ref) {
final timelineUsers = ref.watch(timelineUsersProvider).valueOrNull ?? []; final user = ref.watch(currentUserProvider);
final mapService = ref.watch(mapFactoryProvider).main(timelineUsers); if (user == null) {
throw Exception('User must be logged in to access map');
}
final mapService = ref.watch(mapFactoryProvider).remote(user.id);
ref.onDispose(mapService.dispose); ref.onDispose(mapService.dispose);
return mapService; return mapService;
}, },