diff --git a/app/lib/widget/collection_browser.dart b/app/lib/widget/collection_browser.dart index 7f5b1073..bbd41467 100644 --- a/app/lib/widget/collection_browser.dart +++ b/app/lib/widget/collection_browser.dart @@ -22,6 +22,7 @@ import 'package:nc_photos/controller/collection_items_controller.dart'; import 'package:nc_photos/controller/collections_controller.dart'; import 'package:nc_photos/controller/files_controller.dart'; import 'package:nc_photos/controller/pref_controller.dart'; +import 'package:nc_photos/db/entity_converter.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/download_handler.dart'; import 'package:nc_photos/entity/collection.dart'; @@ -44,6 +45,7 @@ import 'package:nc_photos/session_storage.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/stream_util.dart'; import 'package:nc_photos/widget/album_share_outlier_browser.dart'; +import 'package:nc_photos/widget/app_bar_circular_progress_indicator.dart'; import 'package:nc_photos/widget/app_intermediate_circular_progress_indicator.dart'; import 'package:nc_photos/widget/collection_picker.dart'; import 'package:nc_photos/widget/draggable_item_list.dart'; @@ -63,8 +65,11 @@ import 'package:nc_photos/widget/simple_input_dialog.dart'; import 'package:nc_photos/widget/sliver_visualized_scale.dart'; import 'package:nc_photos/widget/viewer.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/object_util.dart'; import 'package:np_common/or_null.dart'; +import 'package:np_common/unique.dart'; import 'package:np_datetime/np_datetime.dart'; +import 'package:np_db/np_db.dart'; import 'package:np_gps_map/np_gps_map.dart'; import 'package:np_ui/np_ui.dart'; import 'package:to_string/to_string.dart'; @@ -117,6 +122,7 @@ class CollectionBrowser extends StatelessWidget { prefController: context.read(), collectionsController: accountController.collectionsController, filesController: accountController.filesController, + db: context.read(), collection: collection, ), child: const _WrappedCollectionBrowser(), @@ -220,6 +226,29 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser> } }, ), + _BlocListenerT( + selector: (state) => state.placePickerRequest, + listener: (context, placePickerRequest) async { + if (placePickerRequest.value != null) { + final result = + await Navigator.of(context).pushNamed( + PlacePicker.routeName, + arguments: PlacePickerArguments( + initialPosition: + placePickerRequest.value!.initialPosition, + initialZoom: + placePickerRequest.value!.initialPosition == null + ? null + : 15.5, + ), + ); + if (result == null) { + return; + } + context.read<_Bloc>().add(_AddMapToCollection(result)); + } + }, + ), _BlocListenerT( selector: (state) => state.error, listener: (context, error) { @@ -418,6 +447,7 @@ typedef _BlocBuilder = BlocBuilder<_Bloc, _State>; typedef _BlocListener = BlocListener<_Bloc, _State>; typedef _BlocListenerT = BlocListenerT<_Bloc, _State, T>; typedef _BlocSelector = BlocSelector<_Bloc, _State, T>; +typedef _Emitter = Emitter<_State>; extension on BuildContext { _Bloc get bloc => read<_Bloc>(); diff --git a/app/lib/widget/collection_browser.g.dart b/app/lib/widget/collection_browser.g.dart index afe44c02..7dae0d78 100644 --- a/app/lib/widget/collection_browser.g.dart +++ b/app/lib/widget/collection_browser.g.dart @@ -31,6 +31,8 @@ abstract class $_StateCopyWithWorker { List? editItems, List<_Item>? editTransformedItems, CollectionItemSort? editSort, + bool? isAddMapBusy, + Unique<_PlacePickerRequest?>? placePickerRequest, bool? isDragging, int? zoom, double? scale, @@ -61,6 +63,8 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { dynamic editItems = copyWithNull, dynamic editTransformedItems = copyWithNull, dynamic editSort = copyWithNull, + dynamic isAddMapBusy, + dynamic placePickerRequest, dynamic isDragging, dynamic zoom, dynamic scale = copyWithNull, @@ -99,6 +103,10 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { editSort: editSort == copyWithNull ? that.editSort : editSort as CollectionItemSort?, + isAddMapBusy: isAddMapBusy as bool? ?? that.isAddMapBusy, + placePickerRequest: + placePickerRequest as Unique<_PlacePickerRequest?>? ?? + that.placePickerRequest, isDragging: isDragging as bool? ?? that.isDragging, zoom: zoom as int? ?? that.zoom, scale: scale == copyWithNull ? that.scale : scale as double?, @@ -151,7 +159,7 @@ extension _$_BlocNpLog on _Bloc { extension _$_StateToString on _State { String _$toString() { // ignore: unnecessary_string_interpolations - return "_State {collection: $collection, coverUrl: $coverUrl, items: [length: ${items.length}], rawItems: [length: ${rawItems.length}], itemsWhitelist: ${itemsWhitelist == null ? null : "{length: ${itemsWhitelist!.length}}"}, isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, isSelectionRemovable: $isSelectionRemovable, isSelectionManageableFile: $isSelectionManageableFile, isSelectionDeletable: $isSelectionDeletable, isEditMode: $isEditMode, isEditBusy: $isEditBusy, editName: $editName, editItems: ${editItems == null ? null : "[length: ${editItems!.length}]"}, editTransformedItems: ${editTransformedItems == null ? null : "[length: ${editTransformedItems!.length}]"}, editSort: ${editSort == null ? null : "${editSort!.name}"}, isDragging: $isDragging, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, importResult: $importResult, error: $error, message: $message}"; + return "_State {collection: $collection, coverUrl: $coverUrl, items: [length: ${items.length}], rawItems: [length: ${rawItems.length}], itemsWhitelist: ${itemsWhitelist == null ? null : "{length: ${itemsWhitelist!.length}}"}, isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, isSelectionRemovable: $isSelectionRemovable, isSelectionManageableFile: $isSelectionManageableFile, isSelectionDeletable: $isSelectionDeletable, isEditMode: $isEditMode, isEditBusy: $isEditBusy, editName: $editName, editItems: ${editItems == null ? null : "[length: ${editItems!.length}]"}, editTransformedItems: ${editTransformedItems == null ? null : "[length: ${editTransformedItems!.length}]"}, editSort: ${editSort == null ? null : "${editSort!.name}"}, isAddMapBusy: $isAddMapBusy, placePickerRequest: $placePickerRequest, isDragging: $isDragging, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, importResult: $importResult, error: $error, message: $message}"; } } @@ -219,6 +227,13 @@ extension _$_AddLabelToCollectionToString on _AddLabelToCollection { } } +extension _$_RequestAddMapToString on _RequestAddMap { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_RequestAddMap {}"; + } +} + extension _$_AddMapToCollectionToString on _AddMapToCollection { String _$toString() { // ignore: unnecessary_string_interpolations diff --git a/app/lib/widget/collection_browser/app_bar.dart b/app/lib/widget/collection_browser/app_bar.dart index 85802dbd..d8f56109 100644 --- a/app/lib/widget/collection_browser/app_bar.dart +++ b/app/lib/widget/collection_browser/app_bar.dart @@ -365,10 +365,20 @@ class _EditAppBar extends StatelessWidget { onPressed: () => _onAddTextPressed(context), ), if (capabilitiesAdapter.isPermitted(CollectionCapability.mapItem)) - IconButton( - icon: const Icon(Icons.map_outlined), - tooltip: L10n.global().albumAddMapTooltip, - onPressed: () => _onAddMapPressed(context), + _BlocSelector( + selector: (state) => state.isAddMapBusy, + builder: (context, isAddMapBusy) => isAddMapBusy + ? const IconButton( + icon: AppBarProgressIndicator(), + onPressed: null, + ) + : IconButton( + icon: const Icon(Icons.map_outlined), + tooltip: L10n.global().albumAddMapTooltip, + onPressed: () { + context.addEvent(const _RequestAddMap()); + }, + ), ), if (capabilitiesAdapter.isPermitted(CollectionCapability.sort)) IconButton( @@ -393,15 +403,6 @@ class _EditAppBar extends StatelessWidget { context.read<_Bloc>().add(_AddLabelToCollection(result)); } - Future _onAddMapPressed(BuildContext context) async { - final result = await Navigator.of(context) - .pushNamed(PlacePicker.routeName); - if (result == null) { - return; - } - context.read<_Bloc>().add(_AddMapToCollection(result)); - } - Future _onSortPressed(BuildContext context) async { final current = context .read<_Bloc>() diff --git a/app/lib/widget/collection_browser/bloc.dart b/app/lib/widget/collection_browser/bloc.dart index 941345c3..987c4282 100644 --- a/app/lib/widget/collection_browser/bloc.dart +++ b/app/lib/widget/collection_browser/bloc.dart @@ -9,6 +9,7 @@ class _Bloc extends Bloc<_Event, _State> required this.prefController, required this.collectionsController, required this.filesController, + required this.db, required Collection collection, }) : _c = container, _isAdHocCollection = !collectionsController.stream.value.data @@ -30,6 +31,7 @@ class _Bloc extends Bloc<_Event, _State> on<_BeginEdit>(_onBeginEdit); on<_EditName>(_onEditName); on<_AddLabelToCollection>(_onAddLabelToCollection); + on<_RequestAddMap>(_onRequestAddMap); on<_AddMapToCollection>(_onAddMapToCollection); on<_EditSort>(_onEditSort); on<_EditManualSort>(_onEditManualSort); @@ -212,6 +214,34 @@ class _Bloc extends Bloc<_Event, _State> )); } + Future _onRequestAddMap(_RequestAddMap ev, _Emitter emit) async { + _log.info(ev); + emit(state.copyWith(isAddMapBusy: true)); + try { + final location = await db.getFirstLocationOfFileIds( + account: account.toDb(), + fileIds: state.transformedItems + .whereType<_FileItem>() + .map((e) => e.file.fdId) + .toList(), + ); + final mapCoord = + location?.let((e) => MapCoord(e.latitude!, e.longitude!)); + emit(state.copyWith( + placePickerRequest: + Unique(_PlacePickerRequest(initialPosition: mapCoord)), + )); + } catch (e, stackTrace) { + _log.severe("[_onRequestAddMap] Failed while getFirstLocationOfFileIds", + e, stackTrace); + emit(state.copyWith( + placePickerRequest: Unique(const _PlacePickerRequest()), + )); + } finally { + emit(state.copyWith(isAddMapBusy: false)); + } + } + void _onAddMapToCollection(_AddMapToCollection ev, Emitter<_State> emit) { _log.info(ev); assert(isCollectionCapabilityPermitted(CollectionCapability.mapItem)); @@ -597,6 +627,7 @@ class _Bloc extends Bloc<_Event, _State> final PrefController prefController; final CollectionsController collectionsController; final FilesController filesController; + final NpDb db; late final CollectionItemsController itemsController; /// Specify if the supplied [collection] is an "inline" one, which means it's diff --git a/app/lib/widget/collection_browser/state_event.dart b/app/lib/widget/collection_browser/state_event.dart index cca6ee1a..f33fc447 100644 --- a/app/lib/widget/collection_browser/state_event.dart +++ b/app/lib/widget/collection_browser/state_event.dart @@ -21,6 +21,8 @@ class _State { this.editItems, this.editTransformedItems, this.editSort, + required this.isAddMapBusy, + required this.placePickerRequest, required this.isDragging, required this.zoom, this.scale, @@ -47,6 +49,8 @@ class _State { isSelectionDeletable: true, isEditMode: false, isEditBusy: false, + isAddMapBusy: false, + placePickerRequest: Unique(null), isDragging: false, zoom: zoom, ); @@ -76,6 +80,8 @@ class _State { final List? editItems; final List<_Item>? editTransformedItems; final CollectionItemSort? editSort; + final bool isAddMapBusy; + final Unique<_PlacePickerRequest?> placePickerRequest; final bool isDragging; @@ -174,6 +180,14 @@ class _AddLabelToCollection implements _Event { final String label; } +@toString +class _RequestAddMap implements _Event { + const _RequestAddMap(); + + @override + String toString() => _$toString(); +} + @toString class _AddMapToCollection implements _Event { const _AddMapToCollection(this.location); diff --git a/app/lib/widget/collection_browser/type.dart b/app/lib/widget/collection_browser/type.dart index 84427acf..3146f831 100644 --- a/app/lib/widget/collection_browser/type.dart +++ b/app/lib/widget/collection_browser/type.dart @@ -221,6 +221,14 @@ class _DateItem extends _Item { final Date date; } +class _PlacePickerRequest { + const _PlacePickerRequest({ + this.initialPosition, + }); + + final MapCoord? initialPosition; +} + @toString class _ArchiveFailedError implements Exception { const _ArchiveFailedError(this.count); diff --git a/app/lib/widget/my_app.dart b/app/lib/widget/my_app.dart index fb7a920f..c809637b 100644 --- a/app/lib/widget/my_app.dart +++ b/app/lib/widget/my_app.dart @@ -214,7 +214,6 @@ class _WrappedAppState extends State<_WrappedApp> ArchiveBrowser.routeName: ArchiveBrowser.buildRoute, TrustedCertManager.routeName: TrustedCertManager.buildRoute, MapBrowser.routeName: MapBrowser.buildRoute, - PlacePicker.routeName: PlacePicker.buildRoute, }; Route? _onGenerateRoute(RouteSettings settings) { @@ -242,6 +241,7 @@ class _WrappedAppState extends State<_WrappedApp> route ??= _handleImageEnhancerRoute(settings); route ??= _handleCollectionBrowserRoute(settings); route ??= _handleAccountSettingsRoute(settings); + route ??= _handlePlacePickerRoute(settings); return route; } @@ -551,6 +551,18 @@ class _WrappedAppState extends State<_WrappedApp> return null; } + Route? _handlePlacePickerRoute(RouteSettings settings) { + try { + if (settings.name == PlacePicker.routeName) { + final args = settings.arguments as PlacePickerArguments?; + return PlacePicker.buildRoute(args, settings); + } + } catch (e) { + _log.severe("[_handlePlacePickerRoute] Failed while handling route", e); + } + return null; + } + late final _bloc = context.read<_Bloc>(); final _scaffoldMessengerKey = GlobalKey(); final _navigatorKey = GlobalKey(); diff --git a/app/lib/widget/place_picker/bloc.dart b/app/lib/widget/place_picker/bloc.dart index 74ae7c87..d2967723 100644 --- a/app/lib/widget/place_picker/bloc.dart +++ b/app/lib/widget/place_picker/bloc.dart @@ -2,8 +2,13 @@ part of 'place_picker.dart'; @npLog class _Bloc extends Bloc<_Event, _State> with BlocLogger { - _Bloc() : super(_State.init()) { + _Bloc({ + required this.prefController, + required this.initialPosition, + required this.initialZoom, + }) : super(_State.init()) { on<_SetPosition>(_onSetPosition); + on<_Done>(_onDone); } @override @@ -18,4 +23,17 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger { // _log.info(ev); emit(state.copyWith(position: ev.value)); } + + void _onDone(_Done ev, _Emitter emit) { + _log.info(ev); + if (prefController.mapBrowserPrevPositionValue == null && + state.position != null) { + prefController.setMapBrowserPrevPosition(state.position!.center); + } + emit(state.copyWith(isDone: true)); + } + + final PrefController prefController; + final MapCoord? initialPosition; + final double? initialZoom; } diff --git a/app/lib/widget/place_picker/place_picker.dart b/app/lib/widget/place_picker/place_picker.dart index d950c2b6..5446fbc8 100644 --- a/app/lib/widget/place_picker/place_picker.dart +++ b/app/lib/widget/place_picker/place_picker.dart @@ -14,24 +14,54 @@ part 'bloc.dart'; part 'place_picker.g.dart'; part 'state_event.dart'; +class PlacePickerArguments { + const PlacePickerArguments({ + this.initialPosition, + this.initialZoom, + }); + + final MapCoord? initialPosition; + final double? initialZoom; +} + class PlacePicker extends StatelessWidget { static const routeName = "/place-picker"; - static Route buildRoute(RouteSettings settings) => + static Route buildRoute(PlacePickerArguments? args, RouteSettings settings) => MaterialPageRoute( - builder: (_) => const PlacePicker(), + builder: (_) => PlacePicker.fromArgs(args), settings: settings, ); - const PlacePicker({super.key}); + const PlacePicker({ + super.key, + required this.initialPosition, + required this.initialZoom, + }); + + PlacePicker.fromArgs( + PlacePickerArguments? args, { + Key? key, + }) : this( + key: key, + initialPosition: args?.initialPosition, + initialZoom: args?.initialZoom, + ); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => _Bloc(), + create: (context) => _Bloc( + prefController: context.read(), + initialPosition: initialPosition, + initialZoom: initialZoom, + ), child: const _WrappedPlacePicker(), ); } + + final MapCoord? initialPosition; + final double? initialZoom; } @npLog @@ -40,19 +70,31 @@ class _WrappedPlacePicker extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(L10n.global().placePickerTitle), - leading: IconButton( - onPressed: () { - final position = context.state.position; - _log.info("[build] Position picked: $position"); - Navigator.of(context).pop(position); + return MultiBlocListener( + listeners: [ + _BlocListenerT( + selector: (state) => state.isDone, + listener: (context, isDone) { + if (isDone) { + final position = context.state.position; + _log.info("[build] Position picked: $position"); + Navigator.of(context).pop(position); + } }, - icon: const Icon(Icons.check_outlined), ), + ], + child: Scaffold( + appBar: AppBar( + title: Text(L10n.global().placePickerTitle), + leading: IconButton( + onPressed: () { + context.addEvent(const _Done()); + }, + icon: const Icon(Icons.check_outlined), + ), + ), + body: const _BodyView(), ), - body: const _BodyView(), ); } } @@ -62,15 +104,16 @@ class _BodyView extends StatelessWidget { @override Widget build(BuildContext context) { - final prevPosition = - context.read().mapBrowserPrevPositionValue; + final position = context.bloc.initialPosition ?? + context.bloc.prefController.mapBrowserPrevPositionValue; return ValueStreamBuilderEx( stream: context.read().gpsMapProvider, builder: StreamWidgetBuilder.value( (context, gpsMapProvider) => PlacePickerView( providerHint: gpsMapProvider, - initialPosition: prevPosition ?? const MapCoord(0, 0), - initialZoom: prevPosition == null ? 2.5 : 10, + initialPosition: position ?? const MapCoord(0, 0), + initialZoom: + context.bloc.initialZoom ?? (position == null ? 2.5 : 10), onCameraMove: (position) { context.addEvent(_SetPosition(position)); }, @@ -82,7 +125,7 @@ class _BodyView extends StatelessWidget { // typedef _BlocBuilder = BlocBuilder<_Bloc, _State>; // typedef _BlocListener = BlocListener<_Bloc, _State>; -// typedef _BlocListenerT = BlocListenerT<_Bloc, _State, T>; +typedef _BlocListenerT = BlocListenerT<_Bloc, _State, T>; // typedef _BlocSelector = BlocSelector<_Bloc, _State, T>; typedef _Emitter = Emitter<_State>; diff --git a/app/lib/widget/place_picker/place_picker.g.dart b/app/lib/widget/place_picker/place_picker.g.dart index f8d17d3d..973096dc 100644 --- a/app/lib/widget/place_picker/place_picker.g.dart +++ b/app/lib/widget/place_picker/place_picker.g.dart @@ -13,18 +13,19 @@ part of 'place_picker.dart'; // ************************************************************************** abstract class $_StateCopyWithWorker { - _State call({CameraPosition? position}); + _State call({CameraPosition? position, bool? isDone}); } class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { _$_StateCopyWithWorkerImpl(this.that); @override - _State call({dynamic position = copyWithNull}) { + _State call({dynamic position = copyWithNull, dynamic isDone}) { return _State( position: position == copyWithNull ? that.position - : position as CameraPosition?); + : position as CameraPosition?, + isDone: isDone as bool? ?? that.isDone); } final _State that; @@ -61,7 +62,7 @@ extension _$_BlocNpLog on _Bloc { extension _$_StateToString on _State { String _$toString() { // ignore: unnecessary_string_interpolations - return "_State {position: $position}"; + return "_State {position: $position, isDone: $isDone}"; } } @@ -71,3 +72,10 @@ extension _$_SetPositionToString on _SetPosition { return "_SetPosition {value: $value}"; } } + +extension _$_DoneToString on _Done { + String _$toString() { + // ignore: unnecessary_string_interpolations + return "_Done {}"; + } +} diff --git a/app/lib/widget/place_picker/state_event.dart b/app/lib/widget/place_picker/state_event.dart index ea405ac4..da0a5441 100644 --- a/app/lib/widget/place_picker/state_event.dart +++ b/app/lib/widget/place_picker/state_event.dart @@ -5,14 +5,18 @@ part of 'place_picker.dart'; class _State { const _State({ this.position, + required this.isDone, }); - factory _State.init() => const _State(); + factory _State.init() => const _State( + isDone: false, + ); @override String toString() => _$toString(); final CameraPosition? position; + final bool isDone; } abstract class _Event {} @@ -26,3 +30,11 @@ class _SetPosition implements _Event { final CameraPosition value; } + +@toString +class _Done implements _Event { + const _Done(); + + @override + String toString() => _$toString(); +} diff --git a/np_db/lib/src/api.dart b/np_db/lib/src/api.dart index 44184b9a..8d20d2d5 100644 --- a/np_db/lib/src/api.dart +++ b/np_db/lib/src/api.dart @@ -454,6 +454,13 @@ abstract class NpDb { List? excludeRelativeRoots, }); + /// Return the location data of the first file (sorted by date time in + /// descending order) in a group of files + Future getFirstLocationOfFileIds({ + required DbAccount account, + required List fileIds, + }); + /// Return the latitude, longitude and the file id of all files Future> getImageLatLngWithFileIds({ required DbAccount account, diff --git a/np_db_sqlite/lib/src/database/image_location_extension.dart b/np_db_sqlite/lib/src/database/image_location_extension.dart index 901dfd33..4a186b05 100644 --- a/np_db_sqlite/lib/src/database/image_location_extension.dart +++ b/np_db_sqlite/lib/src/database/image_location_extension.dart @@ -200,6 +200,64 @@ extension SqliteDbImageLocationExtension on SqliteDb { }).get(); } + Future queryFirstImageLocationByFileIds({ + required ByAccount account, + required List fileIds, + }) async { + final candidates = await fileIds.withPartition((sublist) async { + final query = selectOnly(files).join([ + innerJoin(accountFiles, accountFiles.file.equalsExp(files.rowId), + useColumns: false), + if (account.dbAccount != null) ...[ + innerJoin(accounts, accounts.rowId.equalsExp(accountFiles.account), + useColumns: false), + innerJoin(servers, servers.rowId.equalsExp(accounts.server), + useColumns: false), + ], + innerJoin(imageLocations, + imageLocations.accountFile.equalsExp(accountFiles.rowId), + useColumns: false), + ]); + query.addColumns([ + accountFiles.rowId, + accountFiles.bestDateTime, + ]); + + if (account.sqlAccount != null) { + query.where(accountFiles.account.equals(account.sqlAccount!.rowId)); + } else if (account.dbAccount != null) { + query + ..where(servers.address.equals(account.dbAccount!.serverAddress)) + ..where(accounts.userId + .equals(account.dbAccount!.userId.toCaseInsensitiveString())); + } + + query + ..where(files.fileId.isIn(sublist)) + ..where(imageLocations.latitude.isNotNull() & + imageLocations.longitude.isNotNull()) + ..orderBy([OrderingTerm.desc(accountFiles.bestDateTime)]) + ..limit(1); + return [ + await query + .map((r) => ( + rowId: r.read(accountFiles.rowId)!, + dateTime: r.read(accountFiles.bestDateTime)!, + )) + .getSingleOrNull() + ]; + }, _maxByFileIdsSize); + final winner = + candidates.nonNulls.sortedBy((e) => e.dateTime).reversed.firstOrNull; + if (winner == null) { + return null; + } + + final reusltQuery = select(imageLocations) + ..where((t) => t.accountFile.equals(winner.rowId)); + return reusltQuery.getSingleOrNull(); + } + Future> _groupImageLocationsBy({ required ByAccount account, required GeneratedColumn by, diff --git a/np_db_sqlite/lib/src/sqlite_api.dart b/np_db_sqlite/lib/src/sqlite_api.dart index 6acd63ad..a9979e95 100644 --- a/np_db_sqlite/lib/src/sqlite_api.dart +++ b/np_db_sqlite/lib/src/sqlite_api.dart @@ -607,6 +607,20 @@ class NpDbSqlite implements NpDb { ); } + @override + Future getFirstLocationOfFileIds({ + required DbAccount account, + required List fileIds, + }) async { + final sqlObj = await _db.use((db) async { + return await db.queryFirstImageLocationByFileIds( + account: ByAccount.db(account), + fileIds: fileIds, + ); + }); + return sqlObj?.let(ImageLocationConverter.fromSql); + } + @override Future> getImageLatLngWithFileIds({ required DbAccount account,