mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
Improve initial location when adding map to album
This commit is contained in:
parent
281efe0d0d
commit
b85afaac07
14 changed files with 311 additions and 40 deletions
|
@ -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/collections_controller.dart';
|
||||||
import 'package:nc_photos/controller/files_controller.dart';
|
import 'package:nc_photos/controller/files_controller.dart';
|
||||||
import 'package:nc_photos/controller/pref_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/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/collection.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/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/stream_util.dart';
|
import 'package:nc_photos/stream_util.dart';
|
||||||
import 'package:nc_photos/widget/album_share_outlier_browser.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/app_intermediate_circular_progress_indicator.dart';
|
||||||
import 'package:nc_photos/widget/collection_picker.dart';
|
import 'package:nc_photos/widget/collection_picker.dart';
|
||||||
import 'package:nc_photos/widget/draggable_item_list.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/sliver_visualized_scale.dart';
|
||||||
import 'package:nc_photos/widget/viewer.dart';
|
import 'package:nc_photos/widget/viewer.dart';
|
||||||
import 'package:np_codegen/np_codegen.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/or_null.dart';
|
||||||
|
import 'package:np_common/unique.dart';
|
||||||
import 'package:np_datetime/np_datetime.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_gps_map/np_gps_map.dart';
|
||||||
import 'package:np_ui/np_ui.dart';
|
import 'package:np_ui/np_ui.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
@ -117,6 +122,7 @@ class CollectionBrowser extends StatelessWidget {
|
||||||
prefController: context.read(),
|
prefController: context.read(),
|
||||||
collectionsController: accountController.collectionsController,
|
collectionsController: accountController.collectionsController,
|
||||||
filesController: accountController.filesController,
|
filesController: accountController.filesController,
|
||||||
|
db: context.read(),
|
||||||
collection: collection,
|
collection: collection,
|
||||||
),
|
),
|
||||||
child: const _WrappedCollectionBrowser(),
|
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<CameraPosition>(
|
||||||
|
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<ExceptionEvent?>(
|
_BlocListenerT<ExceptionEvent?>(
|
||||||
selector: (state) => state.error,
|
selector: (state) => state.error,
|
||||||
listener: (context, error) {
|
listener: (context, error) {
|
||||||
|
@ -418,6 +447,7 @@ typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||||
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
typedef _Emitter = Emitter<_State>;
|
||||||
|
|
||||||
extension on BuildContext {
|
extension on BuildContext {
|
||||||
_Bloc get bloc => read<_Bloc>();
|
_Bloc get bloc => read<_Bloc>();
|
||||||
|
|
|
@ -31,6 +31,8 @@ abstract class $_StateCopyWithWorker {
|
||||||
List<CollectionItem>? editItems,
|
List<CollectionItem>? editItems,
|
||||||
List<_Item>? editTransformedItems,
|
List<_Item>? editTransformedItems,
|
||||||
CollectionItemSort? editSort,
|
CollectionItemSort? editSort,
|
||||||
|
bool? isAddMapBusy,
|
||||||
|
Unique<_PlacePickerRequest?>? placePickerRequest,
|
||||||
bool? isDragging,
|
bool? isDragging,
|
||||||
int? zoom,
|
int? zoom,
|
||||||
double? scale,
|
double? scale,
|
||||||
|
@ -61,6 +63,8 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
dynamic editItems = copyWithNull,
|
dynamic editItems = copyWithNull,
|
||||||
dynamic editTransformedItems = copyWithNull,
|
dynamic editTransformedItems = copyWithNull,
|
||||||
dynamic editSort = copyWithNull,
|
dynamic editSort = copyWithNull,
|
||||||
|
dynamic isAddMapBusy,
|
||||||
|
dynamic placePickerRequest,
|
||||||
dynamic isDragging,
|
dynamic isDragging,
|
||||||
dynamic zoom,
|
dynamic zoom,
|
||||||
dynamic scale = copyWithNull,
|
dynamic scale = copyWithNull,
|
||||||
|
@ -99,6 +103,10 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
editSort: editSort == copyWithNull
|
editSort: editSort == copyWithNull
|
||||||
? that.editSort
|
? that.editSort
|
||||||
: editSort as CollectionItemSort?,
|
: editSort as CollectionItemSort?,
|
||||||
|
isAddMapBusy: isAddMapBusy as bool? ?? that.isAddMapBusy,
|
||||||
|
placePickerRequest:
|
||||||
|
placePickerRequest as Unique<_PlacePickerRequest?>? ??
|
||||||
|
that.placePickerRequest,
|
||||||
isDragging: isDragging as bool? ?? that.isDragging,
|
isDragging: isDragging as bool? ?? that.isDragging,
|
||||||
zoom: zoom as int? ?? that.zoom,
|
zoom: zoom as int? ?? that.zoom,
|
||||||
scale: scale == copyWithNull ? that.scale : scale as double?,
|
scale: scale == copyWithNull ? that.scale : scale as double?,
|
||||||
|
@ -151,7 +159,7 @@ extension _$_BlocNpLog on _Bloc {
|
||||||
extension _$_StateToString on _State {
|
extension _$_StateToString on _State {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// 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 {
|
extension _$_AddMapToCollectionToString on _AddMapToCollection {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
|
|
|
@ -365,10 +365,20 @@ class _EditAppBar extends StatelessWidget {
|
||||||
onPressed: () => _onAddTextPressed(context),
|
onPressed: () => _onAddTextPressed(context),
|
||||||
),
|
),
|
||||||
if (capabilitiesAdapter.isPermitted(CollectionCapability.mapItem))
|
if (capabilitiesAdapter.isPermitted(CollectionCapability.mapItem))
|
||||||
IconButton(
|
_BlocSelector(
|
||||||
icon: const Icon(Icons.map_outlined),
|
selector: (state) => state.isAddMapBusy,
|
||||||
tooltip: L10n.global().albumAddMapTooltip,
|
builder: (context, isAddMapBusy) => isAddMapBusy
|
||||||
onPressed: () => _onAddMapPressed(context),
|
? 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))
|
if (capabilitiesAdapter.isPermitted(CollectionCapability.sort))
|
||||||
IconButton(
|
IconButton(
|
||||||
|
@ -393,15 +403,6 @@ class _EditAppBar extends StatelessWidget {
|
||||||
context.read<_Bloc>().add(_AddLabelToCollection(result));
|
context.read<_Bloc>().add(_AddLabelToCollection(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onAddMapPressed(BuildContext context) async {
|
|
||||||
final result = await Navigator.of(context)
|
|
||||||
.pushNamed<CameraPosition>(PlacePicker.routeName);
|
|
||||||
if (result == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
context.read<_Bloc>().add(_AddMapToCollection(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onSortPressed(BuildContext context) async {
|
Future<void> _onSortPressed(BuildContext context) async {
|
||||||
final current = context
|
final current = context
|
||||||
.read<_Bloc>()
|
.read<_Bloc>()
|
||||||
|
|
|
@ -9,6 +9,7 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
required this.prefController,
|
required this.prefController,
|
||||||
required this.collectionsController,
|
required this.collectionsController,
|
||||||
required this.filesController,
|
required this.filesController,
|
||||||
|
required this.db,
|
||||||
required Collection collection,
|
required Collection collection,
|
||||||
}) : _c = container,
|
}) : _c = container,
|
||||||
_isAdHocCollection = !collectionsController.stream.value.data
|
_isAdHocCollection = !collectionsController.stream.value.data
|
||||||
|
@ -30,6 +31,7 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
on<_BeginEdit>(_onBeginEdit);
|
on<_BeginEdit>(_onBeginEdit);
|
||||||
on<_EditName>(_onEditName);
|
on<_EditName>(_onEditName);
|
||||||
on<_AddLabelToCollection>(_onAddLabelToCollection);
|
on<_AddLabelToCollection>(_onAddLabelToCollection);
|
||||||
|
on<_RequestAddMap>(_onRequestAddMap);
|
||||||
on<_AddMapToCollection>(_onAddMapToCollection);
|
on<_AddMapToCollection>(_onAddMapToCollection);
|
||||||
on<_EditSort>(_onEditSort);
|
on<_EditSort>(_onEditSort);
|
||||||
on<_EditManualSort>(_onEditManualSort);
|
on<_EditManualSort>(_onEditManualSort);
|
||||||
|
@ -212,6 +214,34 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _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) {
|
void _onAddMapToCollection(_AddMapToCollection ev, Emitter<_State> emit) {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
assert(isCollectionCapabilityPermitted(CollectionCapability.mapItem));
|
assert(isCollectionCapabilityPermitted(CollectionCapability.mapItem));
|
||||||
|
@ -597,6 +627,7 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
final PrefController prefController;
|
final PrefController prefController;
|
||||||
final CollectionsController collectionsController;
|
final CollectionsController collectionsController;
|
||||||
final FilesController filesController;
|
final FilesController filesController;
|
||||||
|
final NpDb db;
|
||||||
late final CollectionItemsController itemsController;
|
late final CollectionItemsController itemsController;
|
||||||
|
|
||||||
/// Specify if the supplied [collection] is an "inline" one, which means it's
|
/// Specify if the supplied [collection] is an "inline" one, which means it's
|
||||||
|
|
|
@ -21,6 +21,8 @@ class _State {
|
||||||
this.editItems,
|
this.editItems,
|
||||||
this.editTransformedItems,
|
this.editTransformedItems,
|
||||||
this.editSort,
|
this.editSort,
|
||||||
|
required this.isAddMapBusy,
|
||||||
|
required this.placePickerRequest,
|
||||||
required this.isDragging,
|
required this.isDragging,
|
||||||
required this.zoom,
|
required this.zoom,
|
||||||
this.scale,
|
this.scale,
|
||||||
|
@ -47,6 +49,8 @@ class _State {
|
||||||
isSelectionDeletable: true,
|
isSelectionDeletable: true,
|
||||||
isEditMode: false,
|
isEditMode: false,
|
||||||
isEditBusy: false,
|
isEditBusy: false,
|
||||||
|
isAddMapBusy: false,
|
||||||
|
placePickerRequest: Unique(null),
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
zoom: zoom,
|
zoom: zoom,
|
||||||
);
|
);
|
||||||
|
@ -76,6 +80,8 @@ class _State {
|
||||||
final List<CollectionItem>? editItems;
|
final List<CollectionItem>? editItems;
|
||||||
final List<_Item>? editTransformedItems;
|
final List<_Item>? editTransformedItems;
|
||||||
final CollectionItemSort? editSort;
|
final CollectionItemSort? editSort;
|
||||||
|
final bool isAddMapBusy;
|
||||||
|
final Unique<_PlacePickerRequest?> placePickerRequest;
|
||||||
|
|
||||||
final bool isDragging;
|
final bool isDragging;
|
||||||
|
|
||||||
|
@ -174,6 +180,14 @@ class _AddLabelToCollection implements _Event {
|
||||||
final String label;
|
final String label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _RequestAddMap implements _Event {
|
||||||
|
const _RequestAddMap();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class _AddMapToCollection implements _Event {
|
class _AddMapToCollection implements _Event {
|
||||||
const _AddMapToCollection(this.location);
|
const _AddMapToCollection(this.location);
|
||||||
|
|
|
@ -221,6 +221,14 @@ class _DateItem extends _Item {
|
||||||
final Date date;
|
final Date date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PlacePickerRequest {
|
||||||
|
const _PlacePickerRequest({
|
||||||
|
this.initialPosition,
|
||||||
|
});
|
||||||
|
|
||||||
|
final MapCoord? initialPosition;
|
||||||
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class _ArchiveFailedError implements Exception {
|
class _ArchiveFailedError implements Exception {
|
||||||
const _ArchiveFailedError(this.count);
|
const _ArchiveFailedError(this.count);
|
||||||
|
|
|
@ -214,7 +214,6 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
ArchiveBrowser.routeName: ArchiveBrowser.buildRoute,
|
ArchiveBrowser.routeName: ArchiveBrowser.buildRoute,
|
||||||
TrustedCertManager.routeName: TrustedCertManager.buildRoute,
|
TrustedCertManager.routeName: TrustedCertManager.buildRoute,
|
||||||
MapBrowser.routeName: MapBrowser.buildRoute,
|
MapBrowser.routeName: MapBrowser.buildRoute,
|
||||||
PlacePicker.routeName: PlacePicker.buildRoute,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
|
Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
|
||||||
|
@ -242,6 +241,7 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
route ??= _handleImageEnhancerRoute(settings);
|
route ??= _handleImageEnhancerRoute(settings);
|
||||||
route ??= _handleCollectionBrowserRoute(settings);
|
route ??= _handleCollectionBrowserRoute(settings);
|
||||||
route ??= _handleAccountSettingsRoute(settings);
|
route ??= _handleAccountSettingsRoute(settings);
|
||||||
|
route ??= _handlePlacePickerRoute(settings);
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,6 +551,18 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Route<dynamic>? _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>();
|
late final _bloc = context.read<_Bloc>();
|
||||||
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
|
||||||
final _navigatorKey = GlobalKey<NavigatorState>();
|
final _navigatorKey = GlobalKey<NavigatorState>();
|
||||||
|
|
|
@ -2,8 +2,13 @@ part of 'place_picker.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
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<_SetPosition>(_onSetPosition);
|
||||||
|
on<_Done>(_onDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -18,4 +23,17 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
// _log.info(ev);
|
// _log.info(ev);
|
||||||
emit(state.copyWith(position: ev.value));
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,24 +14,54 @@ part 'bloc.dart';
|
||||||
part 'place_picker.g.dart';
|
part 'place_picker.g.dart';
|
||||||
part 'state_event.dart';
|
part 'state_event.dart';
|
||||||
|
|
||||||
|
class PlacePickerArguments {
|
||||||
|
const PlacePickerArguments({
|
||||||
|
this.initialPosition,
|
||||||
|
this.initialZoom,
|
||||||
|
});
|
||||||
|
|
||||||
|
final MapCoord? initialPosition;
|
||||||
|
final double? initialZoom;
|
||||||
|
}
|
||||||
|
|
||||||
class PlacePicker extends StatelessWidget {
|
class PlacePicker extends StatelessWidget {
|
||||||
static const routeName = "/place-picker";
|
static const routeName = "/place-picker";
|
||||||
|
|
||||||
static Route buildRoute(RouteSettings settings) =>
|
static Route buildRoute(PlacePickerArguments? args, RouteSettings settings) =>
|
||||||
MaterialPageRoute<CameraPosition>(
|
MaterialPageRoute<CameraPosition>(
|
||||||
builder: (_) => const PlacePicker(),
|
builder: (_) => PlacePicker.fromArgs(args),
|
||||||
settings: settings,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => _Bloc(),
|
create: (context) => _Bloc(
|
||||||
|
prefController: context.read(),
|
||||||
|
initialPosition: initialPosition,
|
||||||
|
initialZoom: initialZoom,
|
||||||
|
),
|
||||||
child: const _WrappedPlacePicker(),
|
child: const _WrappedPlacePicker(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final MapCoord? initialPosition;
|
||||||
|
final double? initialZoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
|
@ -40,19 +70,31 @@ class _WrappedPlacePicker extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return MultiBlocListener(
|
||||||
appBar: AppBar(
|
listeners: [
|
||||||
title: Text(L10n.global().placePickerTitle),
|
_BlocListenerT(
|
||||||
leading: IconButton(
|
selector: (state) => state.isDone,
|
||||||
onPressed: () {
|
listener: (context, isDone) {
|
||||||
final position = context.state.position;
|
if (isDone) {
|
||||||
_log.info("[build] Position picked: $position");
|
final position = context.state.position;
|
||||||
Navigator.of(context).pop(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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final prevPosition =
|
final position = context.bloc.initialPosition ??
|
||||||
context.read<PrefController>().mapBrowserPrevPositionValue;
|
context.bloc.prefController.mapBrowserPrevPositionValue;
|
||||||
return ValueStreamBuilderEx<GpsMapProvider>(
|
return ValueStreamBuilderEx<GpsMapProvider>(
|
||||||
stream: context.read<PrefController>().gpsMapProvider,
|
stream: context.read<PrefController>().gpsMapProvider,
|
||||||
builder: StreamWidgetBuilder.value(
|
builder: StreamWidgetBuilder.value(
|
||||||
(context, gpsMapProvider) => PlacePickerView(
|
(context, gpsMapProvider) => PlacePickerView(
|
||||||
providerHint: gpsMapProvider,
|
providerHint: gpsMapProvider,
|
||||||
initialPosition: prevPosition ?? const MapCoord(0, 0),
|
initialPosition: position ?? const MapCoord(0, 0),
|
||||||
initialZoom: prevPosition == null ? 2.5 : 10,
|
initialZoom:
|
||||||
|
context.bloc.initialZoom ?? (position == null ? 2.5 : 10),
|
||||||
onCameraMove: (position) {
|
onCameraMove: (position) {
|
||||||
context.addEvent(_SetPosition(position));
|
context.addEvent(_SetPosition(position));
|
||||||
},
|
},
|
||||||
|
@ -82,7 +125,7 @@ class _BodyView extends StatelessWidget {
|
||||||
|
|
||||||
// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
// typedef _BlocListener = BlocListener<_Bloc, _State>;
|
// typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
// typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||||
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
typedef _Emitter = Emitter<_State>;
|
typedef _Emitter = Emitter<_State>;
|
||||||
|
|
||||||
|
|
|
@ -13,18 +13,19 @@ part of 'place_picker.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
abstract class $_StateCopyWithWorker {
|
abstract class $_StateCopyWithWorker {
|
||||||
_State call({CameraPosition? position});
|
_State call({CameraPosition? position, bool? isDone});
|
||||||
}
|
}
|
||||||
|
|
||||||
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
_$_StateCopyWithWorkerImpl(this.that);
|
_$_StateCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_State call({dynamic position = copyWithNull}) {
|
_State call({dynamic position = copyWithNull, dynamic isDone}) {
|
||||||
return _State(
|
return _State(
|
||||||
position: position == copyWithNull
|
position: position == copyWithNull
|
||||||
? that.position
|
? that.position
|
||||||
: position as CameraPosition?);
|
: position as CameraPosition?,
|
||||||
|
isDone: isDone as bool? ?? that.isDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _State that;
|
final _State that;
|
||||||
|
@ -61,7 +62,7 @@ extension _$_BlocNpLog on _Bloc {
|
||||||
extension _$_StateToString on _State {
|
extension _$_StateToString on _State {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// 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}";
|
return "_SetPosition {value: $value}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension _$_DoneToString on _Done {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_Done {}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,14 +5,18 @@ part of 'place_picker.dart';
|
||||||
class _State {
|
class _State {
|
||||||
const _State({
|
const _State({
|
||||||
this.position,
|
this.position,
|
||||||
|
required this.isDone,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory _State.init() => const _State();
|
factory _State.init() => const _State(
|
||||||
|
isDone: false,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => _$toString();
|
String toString() => _$toString();
|
||||||
|
|
||||||
final CameraPosition? position;
|
final CameraPosition? position;
|
||||||
|
final bool isDone;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _Event {}
|
abstract class _Event {}
|
||||||
|
@ -26,3 +30,11 @@ class _SetPosition implements _Event {
|
||||||
|
|
||||||
final CameraPosition value;
|
final CameraPosition value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _Done implements _Event {
|
||||||
|
const _Done();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
}
|
||||||
|
|
|
@ -454,6 +454,13 @@ abstract class NpDb {
|
||||||
List<String>? excludeRelativeRoots,
|
List<String>? excludeRelativeRoots,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Return the location data of the first file (sorted by date time in
|
||||||
|
/// descending order) in a group of files
|
||||||
|
Future<DbLocation?> getFirstLocationOfFileIds({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<int> fileIds,
|
||||||
|
});
|
||||||
|
|
||||||
/// Return the latitude, longitude and the file id of all files
|
/// Return the latitude, longitude and the file id of all files
|
||||||
Future<List<DbImageLatLng>> getImageLatLngWithFileIds({
|
Future<List<DbImageLatLng>> getImageLatLngWithFileIds({
|
||||||
required DbAccount account,
|
required DbAccount account,
|
||||||
|
|
|
@ -200,6 +200,64 @@ extension SqliteDbImageLocationExtension on SqliteDb {
|
||||||
}).get();
|
}).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<ImageLocation?> queryFirstImageLocationByFileIds({
|
||||||
|
required ByAccount account,
|
||||||
|
required List<int> 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<List<ImageLocationGroup>> _groupImageLocationsBy({
|
Future<List<ImageLocationGroup>> _groupImageLocationsBy({
|
||||||
required ByAccount account,
|
required ByAccount account,
|
||||||
required GeneratedColumn<String> by,
|
required GeneratedColumn<String> by,
|
||||||
|
|
|
@ -607,6 +607,20 @@ class NpDbSqlite implements NpDb {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<DbLocation?> getFirstLocationOfFileIds({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<int> 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
|
@override
|
||||||
Future<List<DbImageLatLng>> getImageLatLngWithFileIds({
|
Future<List<DbImageLatLng>> getImageLatLngWithFileIds({
|
||||||
required DbAccount account,
|
required DbAccount account,
|
||||||
|
|
Loading…
Reference in a new issue