mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
Support adding map to client albums
This commit is contained in:
parent
12f6f59a1c
commit
4959f06fec
23 changed files with 393 additions and 18 deletions
|
@ -10,8 +10,10 @@ import 'package:nc_photos/controller/files_controller.dart';
|
|||
import 'package:nc_photos/controller/server_controller.dart';
|
||||
import 'package:nc_photos/di_container.dart';
|
||||
import 'package:nc_photos/entity/collection.dart';
|
||||
import 'package:nc_photos/entity/collection/adapter.dart';
|
||||
import 'package:nc_photos/entity/collection/util.dart';
|
||||
import 'package:nc_photos/entity/collection_item.dart';
|
||||
import 'package:nc_photos/entity/collection_item/new_item.dart';
|
||||
import 'package:nc_photos/entity/collection_item/util.dart';
|
||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||
import 'package:nc_photos/entity/sharee.dart';
|
||||
|
@ -25,6 +27,7 @@ import 'package:nc_photos/use_case/collection/remove_collections.dart';
|
|||
import 'package:nc_photos/use_case/collection/share_collection.dart';
|
||||
import 'package:nc_photos/use_case/collection/unshare_collection.dart';
|
||||
import 'package:np_codegen/np_codegen.dart';
|
||||
import 'package:np_collection/np_collection.dart';
|
||||
import 'package:np_common/or_null.dart';
|
||||
import 'package:np_common/type.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
@ -208,7 +211,19 @@ class CollectionsController {
|
|||
knownItems: (item?.items.isEmpty ?? true) ? null : item!.items,
|
||||
);
|
||||
});
|
||||
_updateCollection(c, items);
|
||||
final newItems = await items?.asyncMap((e) {
|
||||
if (e is NewCollectionItem) {
|
||||
try {
|
||||
return CollectionAdapter.of(_c, account, c).adaptToNewItem(e);
|
||||
} catch (e, stackTrace) {
|
||||
_log.severe("[edit] Failed to adapt new item: $e", e, stackTrace);
|
||||
return Future.value(null);
|
||||
}
|
||||
} else {
|
||||
return Future.value(e);
|
||||
}
|
||||
});
|
||||
_updateCollection(c, newItems?.whereNotNull().toList());
|
||||
} catch (e, stackTrace) {
|
||||
_dataStreamController.addError(e, stackTrace);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||
import 'package:np_codegen/np_codegen.dart';
|
||||
import 'package:np_common/type.dart';
|
||||
import 'package:np_gps_map/np_gps_map.dart';
|
||||
import 'package:np_string/np_string.dart';
|
||||
import 'package:to_string/to_string.dart';
|
||||
|
||||
|
@ -30,6 +31,9 @@ abstract class AlbumItem with EquatableMixin {
|
|||
case AlbumLabelItem._type:
|
||||
return AlbumLabelItem.fromJson(
|
||||
content.cast<String, dynamic>(), addedBy, addedAt);
|
||||
case AlbumMapItem._type:
|
||||
return AlbumMapItem.fromJson(
|
||||
content.cast<String, dynamic>(), addedBy, addedAt);
|
||||
default:
|
||||
_log.shout("[fromJson] Unknown type: $type");
|
||||
throw ArgumentError.value(type, "type");
|
||||
|
@ -42,6 +46,8 @@ abstract class AlbumItem with EquatableMixin {
|
|||
return AlbumFileItem._type;
|
||||
} else if (this is AlbumLabelItem) {
|
||||
return AlbumLabelItem._type;
|
||||
} else if (this is AlbumMapItem) {
|
||||
return AlbumMapItem._type;
|
||||
} else {
|
||||
throw StateError("Unknwon subtype");
|
||||
}
|
||||
|
@ -181,3 +187,60 @@ class AlbumLabelItem extends AlbumItem {
|
|||
|
||||
static const _type = "label";
|
||||
}
|
||||
|
||||
@toString
|
||||
class AlbumMapItem extends AlbumItem {
|
||||
AlbumMapItem({
|
||||
required super.addedBy,
|
||||
required super.addedAt,
|
||||
required this.location,
|
||||
});
|
||||
|
||||
factory AlbumMapItem.fromJson(
|
||||
JsonObj json, CiString addedBy, DateTime addedAt) {
|
||||
return AlbumMapItem(
|
||||
addedBy: addedBy,
|
||||
addedAt: addedAt,
|
||||
location: CameraPosition.fromJson(json["location"]),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => _$toString();
|
||||
|
||||
@override
|
||||
JsonObj toContentJson() {
|
||||
return {
|
||||
"location": location.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
bool compareServerIdentity(AlbumItem other) =>
|
||||
other is AlbumMapItem &&
|
||||
location == other.location &&
|
||||
addedBy == other.addedBy &&
|
||||
addedAt == other.addedAt;
|
||||
|
||||
AlbumMapItem copyWith({
|
||||
CiString? addedBy,
|
||||
DateTime? addedAt,
|
||||
CameraPosition? location,
|
||||
}) {
|
||||
return AlbumMapItem(
|
||||
addedBy: addedBy ?? this.addedBy,
|
||||
addedAt: addedAt ?? this.addedAt,
|
||||
location: location ?? this.location,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
...super.props,
|
||||
location,
|
||||
];
|
||||
|
||||
final CameraPosition location;
|
||||
|
||||
static const _type = "map";
|
||||
}
|
||||
|
|
|
@ -78,3 +78,10 @@ extension _$AlbumLabelItemToString on AlbumLabelItem {
|
|||
return "AlbumLabelItem {addedBy: $addedBy, addedAt: $addedAt, text: $text}";
|
||||
}
|
||||
}
|
||||
|
||||
extension _$AlbumMapItemToString on AlbumMapItem {
|
||||
String _$toString() {
|
||||
// ignore: unnecessary_string_interpolations
|
||||
return "AlbumMapItem {addedBy: $addedBy, addedAt: $addedAt, location: $location}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,6 +88,8 @@ enum CollectionCapability {
|
|||
rename,
|
||||
// text labels
|
||||
labelItem,
|
||||
// maps
|
||||
mapItem,
|
||||
// set the cover image
|
||||
manualCover,
|
||||
// share the collection with other user on the same server
|
||||
|
|
|
@ -54,6 +54,8 @@ class CollectionAlbumAdapter implements CollectionAdapter {
|
|||
return CollectionFileItemAlbumAdapter(i);
|
||||
} else if (i is AlbumLabelItem) {
|
||||
return CollectionLabelItemAlbumAdapter(i);
|
||||
} else if (i is AlbumMapItem) {
|
||||
return CollectionMapItemAlbumAdapter(i);
|
||||
} else {
|
||||
_log.shout("[listItem] Unknown item type: ${i.runtimeType}");
|
||||
throw UnimplementedError("Unknown item type: ${i.runtimeType}");
|
||||
|
@ -100,6 +102,12 @@ class CollectionAlbumAdapter implements CollectionAdapter {
|
|||
addedAt: e.createdAt,
|
||||
text: e.text,
|
||||
);
|
||||
} else if (e is NewCollectionMapItem) {
|
||||
return AlbumMapItem(
|
||||
addedBy: account.userId,
|
||||
addedAt: e.createdAt,
|
||||
location: e.location,
|
||||
);
|
||||
} else {
|
||||
_log.severe("[edit] Unsupported type: ${e.runtimeType}");
|
||||
return null;
|
||||
|
@ -248,6 +256,16 @@ class CollectionAlbumAdapter implements CollectionAdapter {
|
|||
.reversed
|
||||
.firstWhere((e) => e.text == original.text);
|
||||
return CollectionLabelItemAlbumAdapter(item);
|
||||
} else if (original is NewCollectionMapItem) {
|
||||
final item = AlbumStaticProvider.of(_provider.album)
|
||||
.items
|
||||
.whereType<AlbumMapItem>()
|
||||
.sorted((a, b) => a.addedAt.compareTo(b.addedAt))
|
||||
.reversed
|
||||
.firstWhere((e) =>
|
||||
e.location == original.location &&
|
||||
e.addedAt == original.createdAt);
|
||||
return CollectionMapItemAlbumAdapter(item);
|
||||
} else {
|
||||
throw UnsupportedError("Unsupported type: ${original.runtimeType}");
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@ class CollectionAlbumProvider
|
|||
CollectionCapability.manualItem,
|
||||
CollectionCapability.manualSort,
|
||||
CollectionCapability.labelItem,
|
||||
CollectionCapability.mapItem,
|
||||
CollectionCapability.share,
|
||||
],
|
||||
];
|
||||
|
@ -66,6 +67,7 @@ class CollectionAlbumProvider
|
|||
if (album.provider is AlbumStaticProvider) ...[
|
||||
CollectionCapability.manualItem,
|
||||
CollectionCapability.labelItem,
|
||||
CollectionCapability.mapItem,
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
@ -59,6 +59,12 @@ class CollectionExporter {
|
|||
addedAt: clock.now().toUtc(),
|
||||
text: e.text,
|
||||
);
|
||||
} else if (e is CollectionMapItem) {
|
||||
return AlbumMapItem(
|
||||
addedBy: account.userId,
|
||||
addedAt: clock.now().toUtc(),
|
||||
location: e.location,
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||
import 'package:np_gps_map/np_gps_map.dart';
|
||||
|
||||
/// An item in a [Collection]
|
||||
abstract class CollectionItem {
|
||||
|
@ -24,3 +25,13 @@ abstract class CollectionLabelItem implements CollectionItem {
|
|||
Object get id;
|
||||
String get text;
|
||||
}
|
||||
|
||||
abstract class CollectionMapItem implements CollectionItem {
|
||||
const CollectionMapItem();
|
||||
|
||||
/// An object used to identify this instance
|
||||
///
|
||||
/// [id] should be unique and stable
|
||||
Object get id;
|
||||
CameraPosition get location;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:nc_photos/entity/album/item.dart';
|
||||
import 'package:nc_photos/entity/collection_item.dart';
|
||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||
import 'package:np_gps_map/np_gps_map.dart';
|
||||
import 'package:to_string/to_string.dart';
|
||||
|
||||
part 'album_item_adapter.g.dart';
|
||||
|
@ -11,6 +12,8 @@ mixin AlbumAdaptedCollectionItem on CollectionItem {
|
|||
return CollectionFileItemAlbumAdapter(item);
|
||||
} else if (item is AlbumLabelItem) {
|
||||
return CollectionLabelItemAlbumAdapter(item);
|
||||
} else if (item is AlbumMapItem) {
|
||||
return CollectionMapItemAlbumAdapter(item);
|
||||
} else {
|
||||
throw ArgumentError("Unknown type: ${item.runtimeType}");
|
||||
}
|
||||
|
@ -64,3 +67,23 @@ class CollectionLabelItemAlbumAdapter extends CollectionLabelItem
|
|||
|
||||
final AlbumLabelItem item;
|
||||
}
|
||||
|
||||
@toString
|
||||
class CollectionMapItemAlbumAdapter extends CollectionMapItem
|
||||
with AlbumAdaptedCollectionItem {
|
||||
const CollectionMapItemAlbumAdapter(this.item);
|
||||
|
||||
@override
|
||||
String toString() => _$toString();
|
||||
|
||||
@override
|
||||
Object get id => item.addedAt;
|
||||
|
||||
@override
|
||||
CameraPosition get location => item.location;
|
||||
|
||||
@override
|
||||
AlbumItem get albumItem => item;
|
||||
|
||||
final AlbumMapItem item;
|
||||
}
|
||||
|
|
|
@ -21,3 +21,11 @@ extension _$CollectionLabelItemAlbumAdapterToString
|
|||
return "CollectionLabelItemAlbumAdapter {item: $item}";
|
||||
}
|
||||
}
|
||||
|
||||
extension _$CollectionMapItemAlbumAdapterToString
|
||||
on CollectionMapItemAlbumAdapter {
|
||||
String _$toString() {
|
||||
// ignore: unnecessary_string_interpolations
|
||||
return "CollectionMapItemAlbumAdapter {item: $item}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:nc_photos/entity/collection_item.dart';
|
||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||
import 'package:np_gps_map/np_gps_map.dart';
|
||||
import 'package:to_string/to_string.dart';
|
||||
|
||||
part 'new_item.g.dart';
|
||||
|
@ -47,3 +48,23 @@ class NewCollectionLabelItem implements CollectionLabelItem, NewCollectionItem {
|
|||
|
||||
final DateTime createdAt;
|
||||
}
|
||||
|
||||
/// A new [CollectionMapItem]
|
||||
///
|
||||
/// This class is for marking an intermediate item that has recently been added
|
||||
/// but not necessarily persisted yet to the provider of this collection
|
||||
@toString
|
||||
class NewCollectionMapItem implements CollectionMapItem, NewCollectionItem {
|
||||
const NewCollectionMapItem(this.location, this.createdAt);
|
||||
|
||||
@override
|
||||
String toString() => _$toString();
|
||||
|
||||
@override
|
||||
Object get id => createdAt;
|
||||
|
||||
@override
|
||||
final CameraPosition location;
|
||||
|
||||
final DateTime createdAt;
|
||||
}
|
||||
|
|
|
@ -19,3 +19,10 @@ extension _$NewCollectionLabelItemToString on NewCollectionLabelItem {
|
|||
return "NewCollectionLabelItem {text: $text, createdAt: $createdAt}";
|
||||
}
|
||||
}
|
||||
|
||||
extension _$NewCollectionMapItemToString on NewCollectionMapItem {
|
||||
String _$toString() {
|
||||
// ignore: unnecessary_string_interpolations
|
||||
return "NewCollectionMapItem {location: $location, createdAt: $createdAt}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1522,6 +1522,10 @@
|
|||
"description": "Some button can't be removed. This message will be shown instead when user try to do so"
|
||||
},
|
||||
"placePickerTitle": "Pick a place",
|
||||
"albumAddMapTooltip": "Add map",
|
||||
"@albumAddMapTooltip": {
|
||||
"description": "Add a map that display between photos to an album"
|
||||
},
|
||||
|
||||
"errorUnauthenticated": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||
"@errorUnauthenticated": {
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip",
|
||||
"errorUnauthenticated",
|
||||
"errorDisconnected",
|
||||
"errorLocked",
|
||||
|
@ -288,7 +289,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"de": [
|
||||
|
@ -300,7 +302,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"el": [
|
||||
|
@ -457,7 +460,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"es": [
|
||||
|
@ -469,7 +473,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"fi": [
|
||||
|
@ -517,7 +522,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
|
@ -565,7 +571,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"it": [
|
||||
|
@ -618,7 +625,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
|
@ -1008,6 +1016,7 @@
|
|||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip",
|
||||
"errorUnauthenticated",
|
||||
"errorDisconnected",
|
||||
"errorLocked",
|
||||
|
@ -1067,7 +1076,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
|
@ -1135,7 +1145,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
|
@ -1183,7 +1194,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"tr": [
|
||||
|
@ -1195,7 +1207,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
|
@ -1274,7 +1287,8 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
],
|
||||
|
||||
"zh_Hant": [
|
||||
|
@ -1447,6 +1461,7 @@
|
|||
"dragAndDropRearrangeButtons",
|
||||
"customizeCollectionsNavBarDescription",
|
||||
"customizeButtonsUnsupportedWarning",
|
||||
"placePickerTitle"
|
||||
"placePickerTitle",
|
||||
"albumAddMapTooltip"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -36,11 +36,13 @@ import 'package:nc_photos/entity/file_util.dart' as file_util;
|
|||
import 'package:nc_photos/entity/pref.dart';
|
||||
import 'package:nc_photos/exception_event.dart';
|
||||
import 'package:nc_photos/flutter_util.dart' as flutter_util;
|
||||
import 'package:nc_photos/gps_map_util.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/np_api_util.dart';
|
||||
import 'package:nc_photos/object_extension.dart';
|
||||
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_intermediate_circular_progress_indicator.dart';
|
||||
import 'package:nc_photos/widget/collection_picker.dart';
|
||||
|
@ -52,6 +54,7 @@ import 'package:nc_photos/widget/network_thumbnail.dart';
|
|||
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
||||
import 'package:nc_photos/widget/photo_list_item.dart';
|
||||
import 'package:nc_photos/widget/photo_list_util.dart' as photo_list_util;
|
||||
import 'package:nc_photos/widget/place_picker/place_picker.dart';
|
||||
import 'package:nc_photos/widget/selectable_item_list.dart';
|
||||
import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||
import 'package:nc_photos/widget/share_collection_dialog.dart';
|
||||
|
@ -62,6 +65,7 @@ import 'package:nc_photos/widget/viewer.dart';
|
|||
import 'package:np_codegen/np_codegen.dart';
|
||||
import 'package:np_common/or_null.dart';
|
||||
import 'package:np_datetime/np_datetime.dart';
|
||||
import 'package:np_gps_map/np_gps_map.dart';
|
||||
import 'package:np_ui/np_ui.dart';
|
||||
import 'package:to_string/to_string.dart';
|
||||
|
||||
|
@ -413,7 +417,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
|||
typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||
// typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||
|
||||
extension on BuildContext {
|
||||
_Bloc get bloc => read<_Bloc>();
|
||||
|
|
|
@ -219,6 +219,13 @@ extension _$_AddLabelToCollectionToString on _AddLabelToCollection {
|
|||
}
|
||||
}
|
||||
|
||||
extension _$_AddMapToCollectionToString on _AddMapToCollection {
|
||||
String _$toString() {
|
||||
// ignore: unnecessary_string_interpolations
|
||||
return "_AddMapToCollection {location: $location}";
|
||||
}
|
||||
}
|
||||
|
||||
extension _$_EditSortToString on _EditSort {
|
||||
String _$toString() {
|
||||
// ignore: unnecessary_string_interpolations
|
||||
|
|
|
@ -364,6 +364,12 @@ class _EditAppBar extends StatelessWidget {
|
|||
tooltip: L10n.global().albumAddTextTooltip,
|
||||
onPressed: () => _onAddTextPressed(context),
|
||||
),
|
||||
if (capabilitiesAdapter.isPermitted(CollectionCapability.mapItem))
|
||||
IconButton(
|
||||
icon: const Icon(Icons.map_outlined),
|
||||
tooltip: L10n.global().albumAddMapTooltip,
|
||||
onPressed: () => _onAddMapPressed(context),
|
||||
),
|
||||
if (capabilitiesAdapter.isPermitted(CollectionCapability.sort))
|
||||
IconButton(
|
||||
icon: const Icon(Icons.sort_by_alpha_outlined),
|
||||
|
@ -387,6 +393,15 @@ class _EditAppBar extends StatelessWidget {
|
|||
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 {
|
||||
final current = context
|
||||
.read<_Bloc>()
|
||||
|
|
|
@ -30,6 +30,7 @@ class _Bloc extends Bloc<_Event, _State>
|
|||
on<_BeginEdit>(_onBeginEdit);
|
||||
on<_EditName>(_onEditName);
|
||||
on<_AddLabelToCollection>(_onAddLabelToCollection);
|
||||
on<_AddMapToCollection>(_onAddMapToCollection);
|
||||
on<_EditSort>(_onEditSort);
|
||||
on<_EditManualSort>(_onEditManualSort);
|
||||
on<_TransformEditItems>(_onTransformEditItems);
|
||||
|
@ -210,6 +211,17 @@ class _Bloc extends Bloc<_Event, _State>
|
|||
));
|
||||
}
|
||||
|
||||
void _onAddMapToCollection(_AddMapToCollection ev, Emitter<_State> emit) {
|
||||
_log.info(ev);
|
||||
assert(isCollectionCapabilityPermitted(CollectionCapability.mapItem));
|
||||
emit(state.copyWith(
|
||||
editItems: [
|
||||
NewCollectionMapItem(ev.location, clock.now().toUtc()),
|
||||
...state.editItems ?? state.items,
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
void _onEditSort(_EditSort ev, Emitter<_State> emit) {
|
||||
_log.info(ev);
|
||||
final result = _transformItems(state.editItems ?? state.items, ev.sort);
|
||||
|
@ -505,6 +517,15 @@ class _Bloc extends Bloc<_Event, _State>
|
|||
// TODO
|
||||
},
|
||||
));
|
||||
} else if (item is CollectionMapItem) {
|
||||
transformed.add(_MapItem(
|
||||
original: item,
|
||||
id: item.id,
|
||||
location: item.location,
|
||||
onEditPressed: () {
|
||||
// TODO
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
return _TransformResult(
|
||||
|
|
|
@ -30,3 +30,58 @@ class _EditLabelView extends StatelessWidget {
|
|||
final String text;
|
||||
final VoidCallback? onEditPressed;
|
||||
}
|
||||
|
||||
class _MapView extends StatelessWidget {
|
||||
const _MapView({
|
||||
required this.location,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueStreamBuilderEx<GpsMapProvider>(
|
||||
stream: context.read<PrefController>().gpsMapProvider,
|
||||
builder: StreamWidgetBuilder.value(
|
||||
(context, gpsMapProvider) => StaticMap(
|
||||
key: Key(location.toString()),
|
||||
providerHint: gpsMapProvider,
|
||||
location: location,
|
||||
onTap: onTap,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final CameraPosition location;
|
||||
final VoidCallback? onTap;
|
||||
}
|
||||
|
||||
class _EditMapView extends StatelessWidget {
|
||||
const _EditMapView({
|
||||
required this.location,
|
||||
required this.onEditPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: true,
|
||||
child: _MapView(location: location),
|
||||
),
|
||||
Positioned(
|
||||
top: 8,
|
||||
right: 8,
|
||||
child: FloatingActionButton.small(
|
||||
onPressed: onEditPressed,
|
||||
child: const Icon(Icons.edit_outlined),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final CameraPosition location;
|
||||
final VoidCallback? onEditPressed;
|
||||
}
|
||||
|
|
|
@ -174,6 +174,16 @@ class _AddLabelToCollection implements _Event {
|
|||
final String label;
|
||||
}
|
||||
|
||||
@toString
|
||||
class _AddMapToCollection implements _Event {
|
||||
const _AddMapToCollection(this.location);
|
||||
|
||||
@override
|
||||
String toString() => _$toString();
|
||||
|
||||
final CameraPosition location;
|
||||
}
|
||||
|
||||
@toString
|
||||
class _EditSort implements _Event {
|
||||
const _EditSort(this.sort);
|
||||
|
|
|
@ -8,6 +8,7 @@ abstract class _Item implements SelectableItemMetadata, DraggableItemMetadata {
|
|||
Widget buildWidget(BuildContext context);
|
||||
|
||||
Widget? buildDragFeedbackWidget(BuildContext context) => null;
|
||||
Size? dragFeedbackWidgetSize() => null;
|
||||
}
|
||||
|
||||
/// Items backed by an actual [CollectionItem]
|
||||
|
@ -140,6 +141,62 @@ class _LabelItem extends _ActualItem {
|
|||
final VoidCallback? onEditPressed;
|
||||
}
|
||||
|
||||
class _MapItem extends _ActualItem {
|
||||
const _MapItem({
|
||||
required super.original,
|
||||
required this.id,
|
||||
required this.location,
|
||||
required this.onEditPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
bool get isDraggable => true;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) || (other is _MapItem && id == other.id);
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
|
||||
@override
|
||||
StaggeredTile get staggeredTile => const StaggeredTile.extent(99, 256);
|
||||
|
||||
@override
|
||||
Widget buildWidget(BuildContext context) {
|
||||
return _BlocSelector(
|
||||
selector: (state) => state.isEditMode,
|
||||
builder: (context, isEditMode) => isEditMode
|
||||
? _EditMapView(
|
||||
location: location,
|
||||
onEditPressed: onEditPressed,
|
||||
)
|
||||
: _MapView(
|
||||
location: location,
|
||||
onTap: () {
|
||||
launchExternalMap(location);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? buildDragFeedbackWidget(BuildContext context) {
|
||||
return Icon(
|
||||
Icons.place,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
size: 48,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size? dragFeedbackWidgetSize() => const Size.square(48);
|
||||
|
||||
final Object id;
|
||||
final CameraPosition location;
|
||||
final VoidCallback? onEditPressed;
|
||||
}
|
||||
|
||||
class _DateItem extends _Item {
|
||||
const _DateItem({
|
||||
required this.date,
|
||||
|
|
|
@ -120,6 +120,8 @@ class _EditContentList extends StatelessWidget {
|
|||
itemDragFeedbackBuilder: (context, _, item) =>
|
||||
item.buildDragFeedbackWidget(context) ??
|
||||
item.buildWidget(context),
|
||||
itemDragFeedbackSize: (_, item) =>
|
||||
item.dragFeedbackWidgetSize(),
|
||||
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
||||
onDragResult: (results) {
|
||||
context.addEvent(_EditManualSort(results));
|
||||
|
|
|
@ -24,6 +24,7 @@ class DraggableItemList<T extends DraggableItemMetadata>
|
|||
required this.maxCrossAxisExtent,
|
||||
required this.itemBuilder,
|
||||
required this.itemDragFeedbackBuilder,
|
||||
this.itemDragFeedbackSize,
|
||||
required this.staggeredTileBuilder,
|
||||
this.onDragResult,
|
||||
this.onDraggingChanged,
|
||||
|
@ -38,6 +39,7 @@ class DraggableItemList<T extends DraggableItemMetadata>
|
|||
itemBuilder;
|
||||
final Widget? Function(BuildContext context, int index, T metadata)?
|
||||
itemDragFeedbackBuilder;
|
||||
final Size? Function(int index, T metadata)? itemDragFeedbackSize;
|
||||
final StaggeredTile? Function(int index, T metadata) staggeredTileBuilder;
|
||||
|
||||
/// Called when an item is dropped to a new place
|
||||
|
@ -63,9 +65,9 @@ class _DraggableItemListState<T extends DraggableItemMetadata>
|
|||
if (meta.isDraggable) {
|
||||
return my.Draggable<_DraggableData>(
|
||||
data: _DraggableData(i, meta),
|
||||
feedback: SizedBox(
|
||||
width: widget.maxCrossAxisExtent * .65,
|
||||
height: widget.maxCrossAxisExtent * .65,
|
||||
feedback: SizedBox.fromSize(
|
||||
size: widget.itemDragFeedbackSize?.call(i, meta) ??
|
||||
Size.square(widget.maxCrossAxisExtent * .65),
|
||||
child: widget.itemDragFeedbackBuilder?.call(context, i, meta),
|
||||
),
|
||||
onDropBefore: (data) => _onMoved(data.index, i, true),
|
||||
|
|
Loading…
Reference in a new issue