mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
Merge branch 'album-map'
This commit is contained in:
commit
17916a8434
42 changed files with 872 additions and 129 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/controller/server_controller.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/collection.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/util.dart';
|
||||||
import 'package:nc_photos/entity/collection_item.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/collection_item/util.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sharee.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/share_collection.dart';
|
||||||
import 'package:nc_photos/use_case/collection/unshare_collection.dart';
|
import 'package:nc_photos/use_case/collection/unshare_collection.dart';
|
||||||
import 'package:np_codegen/np_codegen.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/or_null.dart';
|
||||||
import 'package:np_common/type.dart';
|
import 'package:np_common/type.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
@ -208,7 +211,19 @@ class CollectionsController {
|
||||||
knownItems: (item?.items.isEmpty ?? true) ? null : item!.items,
|
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) {
|
} catch (e, stackTrace) {
|
||||||
_dataStreamController.addError(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:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_common/type.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:np_string/np_string.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
@ -30,6 +31,9 @@ abstract class AlbumItem with EquatableMixin {
|
||||||
case AlbumLabelItem._type:
|
case AlbumLabelItem._type:
|
||||||
return AlbumLabelItem.fromJson(
|
return AlbumLabelItem.fromJson(
|
||||||
content.cast<String, dynamic>(), addedBy, addedAt);
|
content.cast<String, dynamic>(), addedBy, addedAt);
|
||||||
|
case AlbumMapItem._type:
|
||||||
|
return AlbumMapItem.fromJson(
|
||||||
|
content.cast<String, dynamic>(), addedBy, addedAt);
|
||||||
default:
|
default:
|
||||||
_log.shout("[fromJson] Unknown type: $type");
|
_log.shout("[fromJson] Unknown type: $type");
|
||||||
throw ArgumentError.value(type, "type");
|
throw ArgumentError.value(type, "type");
|
||||||
|
@ -42,6 +46,8 @@ abstract class AlbumItem with EquatableMixin {
|
||||||
return AlbumFileItem._type;
|
return AlbumFileItem._type;
|
||||||
} else if (this is AlbumLabelItem) {
|
} else if (this is AlbumLabelItem) {
|
||||||
return AlbumLabelItem._type;
|
return AlbumLabelItem._type;
|
||||||
|
} else if (this is AlbumMapItem) {
|
||||||
|
return AlbumMapItem._type;
|
||||||
} else {
|
} else {
|
||||||
throw StateError("Unknwon subtype");
|
throw StateError("Unknwon subtype");
|
||||||
}
|
}
|
||||||
|
@ -181,3 +187,60 @@ class AlbumLabelItem extends AlbumItem {
|
||||||
|
|
||||||
static const _type = "label";
|
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}";
|
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,
|
rename,
|
||||||
// text labels
|
// text labels
|
||||||
labelItem,
|
labelItem,
|
||||||
|
// maps
|
||||||
|
mapItem,
|
||||||
// set the cover image
|
// set the cover image
|
||||||
manualCover,
|
manualCover,
|
||||||
// share the collection with other user on the same server
|
// share the collection with other user on the same server
|
||||||
|
|
|
@ -54,6 +54,8 @@ class CollectionAlbumAdapter implements CollectionAdapter {
|
||||||
return CollectionFileItemAlbumAdapter(i);
|
return CollectionFileItemAlbumAdapter(i);
|
||||||
} else if (i is AlbumLabelItem) {
|
} else if (i is AlbumLabelItem) {
|
||||||
return CollectionLabelItemAlbumAdapter(i);
|
return CollectionLabelItemAlbumAdapter(i);
|
||||||
|
} else if (i is AlbumMapItem) {
|
||||||
|
return CollectionMapItemAlbumAdapter(i);
|
||||||
} else {
|
} else {
|
||||||
_log.shout("[listItem] Unknown item type: ${i.runtimeType}");
|
_log.shout("[listItem] Unknown item type: ${i.runtimeType}");
|
||||||
throw UnimplementedError("Unknown item type: ${i.runtimeType}");
|
throw UnimplementedError("Unknown item type: ${i.runtimeType}");
|
||||||
|
@ -100,6 +102,12 @@ class CollectionAlbumAdapter implements CollectionAdapter {
|
||||||
addedAt: e.createdAt,
|
addedAt: e.createdAt,
|
||||||
text: e.text,
|
text: e.text,
|
||||||
);
|
);
|
||||||
|
} else if (e is NewCollectionMapItem) {
|
||||||
|
return AlbumMapItem(
|
||||||
|
addedBy: account.userId,
|
||||||
|
addedAt: e.createdAt,
|
||||||
|
location: e.location,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
_log.severe("[edit] Unsupported type: ${e.runtimeType}");
|
_log.severe("[edit] Unsupported type: ${e.runtimeType}");
|
||||||
return null;
|
return null;
|
||||||
|
@ -248,6 +256,16 @@ class CollectionAlbumAdapter implements CollectionAdapter {
|
||||||
.reversed
|
.reversed
|
||||||
.firstWhere((e) => e.text == original.text);
|
.firstWhere((e) => e.text == original.text);
|
||||||
return CollectionLabelItemAlbumAdapter(item);
|
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 {
|
} else {
|
||||||
throw UnsupportedError("Unsupported type: ${original.runtimeType}");
|
throw UnsupportedError("Unsupported type: ${original.runtimeType}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ import 'package:nc_photos/entity/collection_item/new_item.dart';
|
||||||
import 'package:nc_photos/entity/collection_item/util.dart';
|
import 'package:nc_photos/entity/collection_item/util.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/nc_album.dart';
|
import 'package:nc_photos/entity/nc_album.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:nc_photos/use_case/find_file_descriptor.dart';
|
import 'package:nc_photos/use_case/find_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/nc_album/add_file_to_nc_album.dart';
|
import 'package:nc_photos/use_case/nc_album/add_file_to_nc_album.dart';
|
||||||
import 'package:nc_photos/use_case/nc_album/edit_nc_album.dart';
|
import 'package:nc_photos/use_case/nc_album/edit_nc_album.dart';
|
||||||
|
@ -98,16 +97,16 @@ class CollectionNcAlbumAdapter
|
||||||
_log.warning(
|
_log.warning(
|
||||||
"[edit] Nextcloud album does not support editing item or sort");
|
"[edit] Nextcloud album does not support editing item or sort");
|
||||||
}
|
}
|
||||||
final newItems = items?.run((items) => items
|
// final newItems = items?.run((items) => items
|
||||||
.map((e) => e is CollectionFileItem ? e.file : null)
|
// .map((e) => e is CollectionFileItem ? e.file : null)
|
||||||
.whereNotNull()
|
// .whereNotNull()
|
||||||
.toList());
|
// .toList());
|
||||||
final newAlbum = await EditNcAlbum(_c)(
|
final newAlbum = await EditNcAlbum(_c)(
|
||||||
account,
|
account,
|
||||||
_provider.album,
|
_provider.album,
|
||||||
name: name,
|
name: name,
|
||||||
items: newItems,
|
// items: newItems,
|
||||||
itemSort: itemSort,
|
// itemSort: itemSort,
|
||||||
);
|
);
|
||||||
return collection.copyWith(
|
return collection.copyWith(
|
||||||
name: name,
|
name: name,
|
||||||
|
|
|
@ -56,6 +56,7 @@ class CollectionAlbumProvider
|
||||||
CollectionCapability.manualItem,
|
CollectionCapability.manualItem,
|
||||||
CollectionCapability.manualSort,
|
CollectionCapability.manualSort,
|
||||||
CollectionCapability.labelItem,
|
CollectionCapability.labelItem,
|
||||||
|
CollectionCapability.mapItem,
|
||||||
CollectionCapability.share,
|
CollectionCapability.share,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -66,6 +67,7 @@ class CollectionAlbumProvider
|
||||||
if (album.provider is AlbumStaticProvider) ...[
|
if (album.provider is AlbumStaticProvider) ...[
|
||||||
CollectionCapability.manualItem,
|
CollectionCapability.manualItem,
|
||||||
CollectionCapability.labelItem,
|
CollectionCapability.labelItem,
|
||||||
|
CollectionCapability.mapItem,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,12 @@ class CollectionExporter {
|
||||||
addedAt: clock.now().toUtc(),
|
addedAt: clock.now().toUtc(),
|
||||||
text: e.text,
|
text: e.text,
|
||||||
);
|
);
|
||||||
|
} else if (e is CollectionMapItem) {
|
||||||
|
return AlbumMapItem(
|
||||||
|
addedBy: account.userId,
|
||||||
|
addedAt: clock.now().toUtc(),
|
||||||
|
location: e.location,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:np_gps_map/np_gps_map.dart';
|
||||||
|
|
||||||
/// An item in a [Collection]
|
/// An item in a [Collection]
|
||||||
abstract class CollectionItem {
|
abstract class CollectionItem {
|
||||||
|
@ -24,3 +25,13 @@ abstract class CollectionLabelItem implements CollectionItem {
|
||||||
Object get id;
|
Object get id;
|
||||||
String get text;
|
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/album/item.dart';
|
||||||
import 'package:nc_photos/entity/collection_item.dart';
|
import 'package:nc_photos/entity/collection_item.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.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';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'album_item_adapter.g.dart';
|
part 'album_item_adapter.g.dart';
|
||||||
|
@ -11,6 +12,8 @@ mixin AlbumAdaptedCollectionItem on CollectionItem {
|
||||||
return CollectionFileItemAlbumAdapter(item);
|
return CollectionFileItemAlbumAdapter(item);
|
||||||
} else if (item is AlbumLabelItem) {
|
} else if (item is AlbumLabelItem) {
|
||||||
return CollectionLabelItemAlbumAdapter(item);
|
return CollectionLabelItemAlbumAdapter(item);
|
||||||
|
} else if (item is AlbumMapItem) {
|
||||||
|
return CollectionMapItemAlbumAdapter(item);
|
||||||
} else {
|
} else {
|
||||||
throw ArgumentError("Unknown type: ${item.runtimeType}");
|
throw ArgumentError("Unknown type: ${item.runtimeType}");
|
||||||
}
|
}
|
||||||
|
@ -64,3 +67,23 @@ class CollectionLabelItemAlbumAdapter extends CollectionLabelItem
|
||||||
|
|
||||||
final AlbumLabelItem item;
|
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}";
|
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/collection_item.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.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';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'new_item.g.dart';
|
part 'new_item.g.dart';
|
||||||
|
@ -47,3 +48,23 @@ class NewCollectionLabelItem implements CollectionLabelItem, NewCollectionItem {
|
||||||
|
|
||||||
final DateTime createdAt;
|
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}";
|
return "NewCollectionLabelItem {text: $text, createdAt: $createdAt}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension _$NewCollectionMapItemToString on NewCollectionMapItem {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "NewCollectionMapItem {location: $location, createdAt: $createdAt}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import 'package:android_intent_plus/android_intent.dart';
|
||||||
import 'package:np_gps_map/np_gps_map.dart';
|
import 'package:np_gps_map/np_gps_map.dart';
|
||||||
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
|
||||||
extension GpsMapProviderExtension on GpsMapProvider {
|
extension GpsMapProviderExtension on GpsMapProvider {
|
||||||
String toUserString() {
|
String toUserString() {
|
||||||
|
@ -10,3 +12,14 @@ extension GpsMapProviderExtension on GpsMapProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void launchExternalMap(CameraPosition location) {
|
||||||
|
if (getRawPlatform() == NpPlatform.android) {
|
||||||
|
final intent = AndroidIntent(
|
||||||
|
action: "action_view",
|
||||||
|
data: Uri.encodeFull(
|
||||||
|
"geo:${location.center.latitude},${location.center.longitude}?z=${location.zoom}"),
|
||||||
|
);
|
||||||
|
intent.launch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1521,6 +1521,11 @@
|
||||||
"@customizeButtonsUnsupportedWarning": {
|
"@customizeButtonsUnsupportedWarning": {
|
||||||
"description": "Some button can't be removed. This message will be shown instead when user try to do so"
|
"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": "Unauthenticated access. Please sign-in again if the problem continues",
|
||||||
"@errorUnauthenticated": {
|
"@errorUnauthenticated": {
|
||||||
|
|
|
@ -268,6 +268,8 @@
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning",
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip",
|
||||||
"errorUnauthenticated",
|
"errorUnauthenticated",
|
||||||
"errorDisconnected",
|
"errorDisconnected",
|
||||||
"errorLocked",
|
"errorLocked",
|
||||||
|
@ -286,7 +288,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
|
@ -297,7 +301,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"el": [
|
"el": [
|
||||||
|
@ -453,7 +459,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
|
@ -464,7 +472,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
|
@ -511,7 +521,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
|
@ -558,7 +570,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
|
@ -610,7 +624,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
|
@ -999,6 +1015,8 @@
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning",
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip",
|
||||||
"errorUnauthenticated",
|
"errorUnauthenticated",
|
||||||
"errorDisconnected",
|
"errorDisconnected",
|
||||||
"errorLocked",
|
"errorLocked",
|
||||||
|
@ -1057,7 +1075,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
|
@ -1124,7 +1144,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
|
@ -1171,7 +1193,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
|
@ -1182,7 +1206,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
|
@ -1260,7 +1286,9 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"placePickerTitle",
|
||||||
|
"albumAddMapTooltip"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh_Hant": [
|
"zh_Hant": [
|
||||||
|
@ -1432,6 +1460,8 @@
|
||||||
"livePhotoTooltip",
|
"livePhotoTooltip",
|
||||||
"dragAndDropRearrangeButtons",
|
"dragAndDropRearrangeButtons",
|
||||||
"customizeCollectionsNavBarDescription",
|
"customizeCollectionsNavBarDescription",
|
||||||
"customizeButtonsUnsupportedWarning"
|
"customizeButtonsUnsupportedWarning",
|
||||||
|
"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/entity/pref.dart';
|
||||||
import 'package:nc_photos/exception_event.dart';
|
import 'package:nc_photos/exception_event.dart';
|
||||||
import 'package:nc_photos/flutter_util.dart' as flutter_util;
|
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/k.dart' as k;
|
||||||
import 'package:nc_photos/np_api_util.dart';
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/session_storage.dart';
|
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/widget/album_share_outlier_browser.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/app_intermediate_circular_progress_indicator.dart';
|
||||||
import 'package:nc_photos/widget/collection_picker.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/page_visibility_mixin.dart';
|
||||||
import 'package:nc_photos/widget/photo_list_item.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/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/selectable_item_list.dart';
|
||||||
import 'package:nc_photos/widget/selection_app_bar.dart';
|
import 'package:nc_photos/widget/selection_app_bar.dart';
|
||||||
import 'package:nc_photos/widget/share_collection_dialog.dart';
|
import 'package:nc_photos/widget/share_collection_dialog.dart';
|
||||||
|
@ -62,12 +65,14 @@ 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/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
import 'package:np_datetime/np_datetime.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:np_ui/np_ui.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'collection_browser.g.dart';
|
part 'collection_browser.g.dart';
|
||||||
part 'collection_browser/app_bar.dart';
|
part 'collection_browser/app_bar.dart';
|
||||||
part 'collection_browser/bloc.dart';
|
part 'collection_browser/bloc.dart';
|
||||||
|
part 'collection_browser/item_view.dart';
|
||||||
part 'collection_browser/state_event.dart';
|
part 'collection_browser/state_event.dart';
|
||||||
part 'collection_browser/type.dart';
|
part 'collection_browser/type.dart';
|
||||||
part 'collection_browser/view.dart';
|
part 'collection_browser/view.dart';
|
||||||
|
@ -412,7 +417,7 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
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>;
|
||||||
|
|
||||||
extension on BuildContext {
|
extension on BuildContext {
|
||||||
_Bloc get bloc => read<_Bloc>();
|
_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 {
|
extension _$_EditSortToString on _EditSort {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
|
|
|
@ -364,6 +364,12 @@ class _EditAppBar extends StatelessWidget {
|
||||||
tooltip: L10n.global().albumAddTextTooltip,
|
tooltip: L10n.global().albumAddTextTooltip,
|
||||||
onPressed: () => _onAddTextPressed(context),
|
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))
|
if (capabilitiesAdapter.isPermitted(CollectionCapability.sort))
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.sort_by_alpha_outlined),
|
icon: const Icon(Icons.sort_by_alpha_outlined),
|
||||||
|
@ -387,6 +393,15 @@ 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>()
|
||||||
|
|
|
@ -30,6 +30,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<_AddMapToCollection>(_onAddMapToCollection);
|
||||||
on<_EditSort>(_onEditSort);
|
on<_EditSort>(_onEditSort);
|
||||||
on<_EditManualSort>(_onEditManualSort);
|
on<_EditManualSort>(_onEditManualSort);
|
||||||
on<_TransformEditItems>(_onTransformEditItems);
|
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) {
|
void _onEditSort(_EditSort ev, Emitter<_State> emit) {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
final result = _transformItems(state.editItems ?? state.items, ev.sort);
|
final result = _transformItems(state.editItems ?? state.items, ev.sort);
|
||||||
|
@ -497,22 +509,23 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
"[_transformItems] Unsupported file format: ${item.file.fdMime}");
|
"[_transformItems] Unsupported file format: ${item.file.fdMime}");
|
||||||
}
|
}
|
||||||
} else if (item is CollectionLabelItem) {
|
} else if (item is CollectionLabelItem) {
|
||||||
if (state.isEditMode) {
|
transformed.add(_LabelItem(
|
||||||
transformed.add(_EditLabelListItem(
|
original: item,
|
||||||
original: item,
|
id: item.id,
|
||||||
id: item.id,
|
text: item.text,
|
||||||
text: item.text,
|
onEditPressed: () {
|
||||||
onEditPressed: () {
|
// TODO
|
||||||
// TODO
|
},
|
||||||
},
|
));
|
||||||
));
|
} else if (item is CollectionMapItem) {
|
||||||
} else {
|
transformed.add(_MapItem(
|
||||||
transformed.add(_LabelItem(
|
original: item,
|
||||||
original: item,
|
id: item.id,
|
||||||
id: item.id,
|
location: item.location,
|
||||||
text: item.text,
|
onEditPressed: () {
|
||||||
));
|
// TODO
|
||||||
}
|
},
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _TransformResult(
|
return _TransformResult(
|
||||||
|
|
87
app/lib/widget/collection_browser/item_view.dart
Normal file
87
app/lib/widget/collection_browser/item_view.dart
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
part of '../collection_browser.dart';
|
||||||
|
|
||||||
|
class _LabelView extends StatelessWidget {
|
||||||
|
const _LabelView({
|
||||||
|
required this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PhotoListLabel(text: text);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditLabelView extends StatelessWidget {
|
||||||
|
const _EditLabelView({
|
||||||
|
required this.text,
|
||||||
|
required this.onEditPressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PhotoListLabelEdit(
|
||||||
|
text: text,
|
||||||
|
onEditPressed: onEditPressed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
final String label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _AddMapToCollection implements _Event {
|
||||||
|
const _AddMapToCollection(this.location);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final CameraPosition location;
|
||||||
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class _EditSort implements _Event {
|
class _EditSort implements _Event {
|
||||||
const _EditSort(this.sort);
|
const _EditSort(this.sort);
|
||||||
|
|
|
@ -8,6 +8,7 @@ abstract class _Item implements SelectableItemMetadata, DraggableItemMetadata {
|
||||||
Widget buildWidget(BuildContext context);
|
Widget buildWidget(BuildContext context);
|
||||||
|
|
||||||
Widget? buildDragFeedbackWidget(BuildContext context) => null;
|
Widget? buildDragFeedbackWidget(BuildContext context) => null;
|
||||||
|
Size? dragFeedbackWidgetSize() => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Items backed by an actual [CollectionItem]
|
/// Items backed by an actual [CollectionItem]
|
||||||
|
@ -101,8 +102,12 @@ class _LabelItem extends _ActualItem {
|
||||||
required super.original,
|
required super.original,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.text,
|
required this.text,
|
||||||
|
required this.onEditPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isDraggable => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) || (other is _LabelItem && id == other.id);
|
identical(this, other) || (other is _LabelItem && id == other.id);
|
||||||
|
@ -115,39 +120,80 @@ class _LabelItem extends _ActualItem {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildWidget(BuildContext context) {
|
Widget buildWidget(BuildContext context) {
|
||||||
return PhotoListLabel(
|
return _BlocSelector(
|
||||||
text: text,
|
selector: (state) => state.isEditMode,
|
||||||
|
builder: (context, isEditMode) => isEditMode
|
||||||
|
? _EditLabelView(
|
||||||
|
text: text,
|
||||||
|
onEditPressed: onEditPressed,
|
||||||
|
)
|
||||||
|
: _LabelView(text: text),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? buildDragFeedbackWidget(BuildContext context) {
|
||||||
|
return _LabelView(text: text);
|
||||||
|
}
|
||||||
|
|
||||||
final Object id;
|
final Object id;
|
||||||
final String text;
|
final String text;
|
||||||
|
final VoidCallback? onEditPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EditLabelListItem extends _LabelItem {
|
class _MapItem extends _ActualItem {
|
||||||
const _EditLabelListItem({
|
const _MapItem({
|
||||||
required super.original,
|
required super.original,
|
||||||
required super.id,
|
required this.id,
|
||||||
required super.text,
|
required this.location,
|
||||||
required this.onEditPressed,
|
required this.onEditPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get isDraggable => true;
|
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
|
@override
|
||||||
Widget buildWidget(BuildContext context) {
|
Widget buildWidget(BuildContext context) {
|
||||||
return PhotoListLabelEdit(
|
return _BlocSelector(
|
||||||
text: text,
|
selector: (state) => state.isEditMode,
|
||||||
onEditPressed: onEditPressed,
|
builder: (context, isEditMode) => isEditMode
|
||||||
|
? _EditMapView(
|
||||||
|
location: location,
|
||||||
|
onEditPressed: onEditPressed,
|
||||||
|
)
|
||||||
|
: _MapView(
|
||||||
|
location: location,
|
||||||
|
onTap: () {
|
||||||
|
launchExternalMap(location);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget? buildDragFeedbackWidget(BuildContext context) {
|
Widget? buildDragFeedbackWidget(BuildContext context) {
|
||||||
return super.buildWidget(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;
|
final VoidCallback? onEditPressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,8 @@ class _EditContentList extends StatelessWidget {
|
||||||
itemDragFeedbackBuilder: (context, _, item) =>
|
itemDragFeedbackBuilder: (context, _, item) =>
|
||||||
item.buildDragFeedbackWidget(context) ??
|
item.buildDragFeedbackWidget(context) ??
|
||||||
item.buildWidget(context),
|
item.buildWidget(context),
|
||||||
|
itemDragFeedbackSize: (_, item) =>
|
||||||
|
item.dragFeedbackWidgetSize(),
|
||||||
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
staggeredTileBuilder: (_, item) => item.staggeredTile,
|
||||||
onDragResult: (results) {
|
onDragResult: (results) {
|
||||||
context.addEvent(_EditManualSort(results));
|
context.addEvent(_EditManualSort(results));
|
||||||
|
|
|
@ -24,6 +24,7 @@ class DraggableItemList<T extends DraggableItemMetadata>
|
||||||
required this.maxCrossAxisExtent,
|
required this.maxCrossAxisExtent,
|
||||||
required this.itemBuilder,
|
required this.itemBuilder,
|
||||||
required this.itemDragFeedbackBuilder,
|
required this.itemDragFeedbackBuilder,
|
||||||
|
this.itemDragFeedbackSize,
|
||||||
required this.staggeredTileBuilder,
|
required this.staggeredTileBuilder,
|
||||||
this.onDragResult,
|
this.onDragResult,
|
||||||
this.onDraggingChanged,
|
this.onDraggingChanged,
|
||||||
|
@ -38,6 +39,7 @@ class DraggableItemList<T extends DraggableItemMetadata>
|
||||||
itemBuilder;
|
itemBuilder;
|
||||||
final Widget? Function(BuildContext context, int index, T metadata)?
|
final Widget? Function(BuildContext context, int index, T metadata)?
|
||||||
itemDragFeedbackBuilder;
|
itemDragFeedbackBuilder;
|
||||||
|
final Size? Function(int index, T metadata)? itemDragFeedbackSize;
|
||||||
final StaggeredTile? Function(int index, T metadata) staggeredTileBuilder;
|
final StaggeredTile? Function(int index, T metadata) staggeredTileBuilder;
|
||||||
|
|
||||||
/// Called when an item is dropped to a new place
|
/// Called when an item is dropped to a new place
|
||||||
|
@ -63,9 +65,9 @@ class _DraggableItemListState<T extends DraggableItemMetadata>
|
||||||
if (meta.isDraggable) {
|
if (meta.isDraggable) {
|
||||||
return my.Draggable<_DraggableData>(
|
return my.Draggable<_DraggableData>(
|
||||||
data: _DraggableData(i, meta),
|
data: _DraggableData(i, meta),
|
||||||
feedback: SizedBox(
|
feedback: SizedBox.fromSize(
|
||||||
width: widget.maxCrossAxisExtent * .65,
|
size: widget.itemDragFeedbackSize?.call(i, meta) ??
|
||||||
height: widget.maxCrossAxisExtent * .65,
|
Size.square(widget.maxCrossAxisExtent * .65),
|
||||||
child: widget.itemDragFeedbackBuilder?.call(context, i, meta),
|
child: widget.itemDragFeedbackBuilder?.call(context, i, meta),
|
||||||
),
|
),
|
||||||
onDropBefore: (data) => _onMoved(data.index, i, true),
|
onDropBefore: (data) => _onMoved(data.index, i, true),
|
||||||
|
|
|
@ -38,6 +38,7 @@ import 'package:nc_photos/widget/image_enhancer.dart';
|
||||||
import 'package:nc_photos/widget/local_file_viewer.dart';
|
import 'package:nc_photos/widget/local_file_viewer.dart';
|
||||||
import 'package:nc_photos/widget/map_browser.dart';
|
import 'package:nc_photos/widget/map_browser.dart';
|
||||||
import 'package:nc_photos/widget/people_browser.dart';
|
import 'package:nc_photos/widget/people_browser.dart';
|
||||||
|
import 'package:nc_photos/widget/place_picker/place_picker.dart';
|
||||||
import 'package:nc_photos/widget/places_browser.dart';
|
import 'package:nc_photos/widget/places_browser.dart';
|
||||||
import 'package:nc_photos/widget/result_viewer.dart';
|
import 'package:nc_photos/widget/result_viewer.dart';
|
||||||
import 'package:nc_photos/widget/root_picker.dart';
|
import 'package:nc_photos/widget/root_picker.dart';
|
||||||
|
@ -213,6 +214,7 @@ 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) {
|
||||||
|
|
21
app/lib/widget/place_picker/bloc.dart
Normal file
21
app/lib/widget/place_picker/bloc.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
part of 'place_picker.dart';
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
_Bloc() : super(_State.init()) {
|
||||||
|
on<_SetPosition>(_onSetPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tag => _log.fullName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool Function(dynamic, dynamic)? get shouldLog => (currentState, nextState) {
|
||||||
|
return currentState.position == nextState.position;
|
||||||
|
};
|
||||||
|
|
||||||
|
void _onSetPosition(_SetPosition ev, _Emitter emit) {
|
||||||
|
// _log.info(ev);
|
||||||
|
emit(state.copyWith(position: ev.value));
|
||||||
|
}
|
||||||
|
}
|
93
app/lib/widget/place_picker/place_picker.dart
Normal file
93
app/lib/widget/place_picker/place_picker.dart
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import 'package:copy_with/copy_with.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/bloc_util.dart';
|
||||||
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
|
import 'package:nc_photos/stream_util.dart';
|
||||||
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_gps_map/np_gps_map.dart';
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'bloc.dart';
|
||||||
|
part 'place_picker.g.dart';
|
||||||
|
part 'state_event.dart';
|
||||||
|
|
||||||
|
class PlacePicker extends StatelessWidget {
|
||||||
|
static const routeName = "/place-picker";
|
||||||
|
|
||||||
|
static Route buildRoute(RouteSettings settings) =>
|
||||||
|
MaterialPageRoute<CameraPosition>(
|
||||||
|
builder: (_) => const PlacePicker(),
|
||||||
|
settings: settings,
|
||||||
|
);
|
||||||
|
|
||||||
|
const PlacePicker({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => _Bloc(),
|
||||||
|
child: const _WrappedPlacePicker(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _WrappedPlacePicker extends StatelessWidget {
|
||||||
|
const _WrappedPlacePicker();
|
||||||
|
|
||||||
|
@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);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.check_outlined),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: const _BodyView(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BodyView extends StatelessWidget {
|
||||||
|
const _BodyView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final prevPosition =
|
||||||
|
context.read<PrefController>().mapBrowserPrevPositionValue;
|
||||||
|
return ValueStreamBuilderEx<GpsMapProvider>(
|
||||||
|
stream: context.read<PrefController>().gpsMapProvider,
|
||||||
|
builder: StreamWidgetBuilder.value(
|
||||||
|
(context, gpsMapProvider) => PlacePickerView(
|
||||||
|
providerHint: gpsMapProvider,
|
||||||
|
initialPosition: prevPosition ?? const MapCoord(0, 0),
|
||||||
|
initialZoom: prevPosition == null ? 2.5 : 10,
|
||||||
|
onCameraMove: (position) {
|
||||||
|
context.addEvent(_SetPosition(position));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 _Emitter = Emitter<_State>;
|
||||||
|
|
||||||
|
extension on BuildContext {
|
||||||
|
_Bloc get bloc => read<_Bloc>();
|
||||||
|
_State get state => bloc.state;
|
||||||
|
void addEvent(_Event event) => bloc.add(event);
|
||||||
|
}
|
73
app/lib/widget/place_picker/place_picker.g.dart
Normal file
73
app/lib/widget/place_picker/place_picker.g.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'place_picker.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithLintRuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: library_private_types_in_public_api, duplicate_ignore
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class $_StateCopyWithWorker {
|
||||||
|
_State call({CameraPosition? position});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
|
_$_StateCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_State call({dynamic position = copyWithNull}) {
|
||||||
|
return _State(
|
||||||
|
position: position == copyWithNull
|
||||||
|
? that.position
|
||||||
|
: position as CameraPosition?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _State that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $_StateCopyWith on _State {
|
||||||
|
$_StateCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$_StateCopyWithWorker get _$copyWith => _$_StateCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// NpLogGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_WrappedPlacePickerNpLog on _WrappedPlacePicker {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log =
|
||||||
|
Logger("widget.place_picker.place_picker._WrappedPlacePicker");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_BlocNpLog on _Bloc {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger("widget.place_picker.place_picker._Bloc");
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_StateToString on _State {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_State {position: $position}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetPositionToString on _SetPosition {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetPosition {value: $value}";
|
||||||
|
}
|
||||||
|
}
|
28
app/lib/widget/place_picker/state_event.dart
Normal file
28
app/lib/widget/place_picker/state_event.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
part of 'place_picker.dart';
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class _State {
|
||||||
|
const _State({
|
||||||
|
this.position,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _State.init() => const _State();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final CameraPosition? position;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Event {}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetPosition implements _Event {
|
||||||
|
const _SetPosition(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final CameraPosition value;
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:android_intent_plus/android_intent.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
@ -19,6 +18,7 @@ import 'package:nc_photos/entity/exif_extension.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
import 'package:nc_photos/gps_map_util.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/platform/features.dart' as features;
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
|
@ -355,11 +355,13 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
height: 256,
|
height: 256,
|
||||||
child: ValueStreamBuilder<GpsMapProvider>(
|
child: ValueStreamBuilder<GpsMapProvider>(
|
||||||
stream: context.read<PrefController>().gpsMapProvider,
|
stream: context.read<PrefController>().gpsMapProvider,
|
||||||
builder: (context, gpsMapProvider) => GpsMap(
|
builder: (context, gpsMapProvider) => StaticMap(
|
||||||
providerHint: gpsMapProvider.requireData,
|
providerHint: gpsMapProvider.requireData,
|
||||||
center: _gps!,
|
location: CameraPosition(center: _gps!, zoom: 16),
|
||||||
zoom: 16,
|
onTap: () => launchExternalMap(CameraPosition(
|
||||||
onTap: _onMapTap,
|
center: _gps!,
|
||||||
|
zoom: 16,
|
||||||
|
)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -493,16 +495,6 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
SetAsHandler(c, context: context).setAsFile(widget.account, _file!);
|
SetAsHandler(c, context: context).setAsFile(widget.account, _file!);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onMapTap() {
|
|
||||||
if (getRawPlatform() == NpPlatform.android) {
|
|
||||||
final intent = AndroidIntent(
|
|
||||||
action: "action_view",
|
|
||||||
data: Uri.encodeFull("geo:${_gps!.latitude},${_gps!.longitude}?z=16"),
|
|
||||||
);
|
|
||||||
intent.launch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onDateTimeTap(BuildContext context) {
|
void _onDateTimeTap(BuildContext context) {
|
||||||
assert(_file != null);
|
assert(_file != null);
|
||||||
showDialog(
|
showDialog(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
library np_gps_map;
|
library np_gps_map;
|
||||||
|
|
||||||
export 'src/gps_map.dart';
|
|
||||||
export 'src/interactive_map.dart';
|
export 'src/interactive_map.dart';
|
||||||
export 'src/map_coord.dart';
|
export 'src/place_picker.dart';
|
||||||
|
export 'src/static_map.dart';
|
||||||
|
export 'src/type.dart';
|
||||||
export 'src/util.dart' show initGpsMap;
|
export 'src/util.dart' show initGpsMap;
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:np_gps_map/src/gps_map.dart';
|
|
||||||
import 'package:np_gps_map/src/interactive_map/google.dart';
|
import 'package:np_gps_map/src/interactive_map/google.dart';
|
||||||
import 'package:np_gps_map/src/interactive_map/osm.dart';
|
import 'package:np_gps_map/src/interactive_map/osm.dart';
|
||||||
import 'package:np_gps_map/src/map_coord.dart';
|
import 'package:np_gps_map/src/type.dart';
|
||||||
import 'package:np_gps_map/src/util.dart';
|
import 'package:np_gps_map/src/util.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
|
||||||
|
@ -31,6 +30,7 @@ class InteractiveMap extends StatelessWidget {
|
||||||
this.googleClusterBuilder,
|
this.googleClusterBuilder,
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.onMapCreated,
|
this.onMapCreated,
|
||||||
|
this.onCameraMove,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -45,6 +45,7 @@ class InteractiveMap extends StatelessWidget {
|
||||||
clusterBuilder: osmClusterBuilder,
|
clusterBuilder: osmClusterBuilder,
|
||||||
contentPadding: contentPadding,
|
contentPadding: contentPadding,
|
||||||
onMapCreated: onMapCreated,
|
onMapCreated: onMapCreated,
|
||||||
|
onCameraMove: onCameraMove,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return GoogleInteractiveMap(
|
return GoogleInteractiveMap(
|
||||||
|
@ -55,6 +56,7 @@ class InteractiveMap extends StatelessWidget {
|
||||||
clusterBuilder: googleClusterBuilder,
|
clusterBuilder: googleClusterBuilder,
|
||||||
contentPadding: contentPadding,
|
contentPadding: contentPadding,
|
||||||
onMapCreated: onMapCreated,
|
onMapCreated: onMapCreated,
|
||||||
|
onCameraMove: onCameraMove,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,4 +72,5 @@ class InteractiveMap extends StatelessWidget {
|
||||||
final OsmClusterBuilder? osmClusterBuilder;
|
final OsmClusterBuilder? osmClusterBuilder;
|
||||||
final EdgeInsets? contentPadding;
|
final EdgeInsets? contentPadding;
|
||||||
final void Function(InteractiveMapController controller)? onMapCreated;
|
final void Function(InteractiveMapController controller)? onMapCreated;
|
||||||
|
final void Function(CameraPosition position)? onCameraMove;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:np_common/object_util.dart';
|
import 'package:np_common/object_util.dart';
|
||||||
import 'package:np_gps_map/src/interactive_map.dart';
|
import 'package:np_gps_map/src/interactive_map.dart';
|
||||||
import 'package:np_gps_map/src/map_coord.dart';
|
import 'package:np_gps_map/src/type.dart' as type;
|
||||||
|
|
||||||
typedef GoogleClusterBuilder = FutureOr<BitmapDescriptor> Function(
|
typedef GoogleClusterBuilder = FutureOr<BitmapDescriptor> Function(
|
||||||
BuildContext context, List<DataPoint> dataPoints);
|
BuildContext context, List<DataPoint> dataPoints);
|
||||||
|
@ -20,18 +20,20 @@ class GoogleInteractiveMap extends StatefulWidget {
|
||||||
this.onClusterTap,
|
this.onClusterTap,
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.onMapCreated,
|
this.onMapCreated,
|
||||||
|
this.onCameraMove,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _GoogleInteractiveMapState();
|
State<StatefulWidget> createState() => _GoogleInteractiveMapState();
|
||||||
|
|
||||||
final MapCoord? initialPosition;
|
final type.MapCoord? initialPosition;
|
||||||
final double? initialZoom;
|
final double? initialZoom;
|
||||||
final List<DataPoint>? dataPoints;
|
final List<DataPoint>? dataPoints;
|
||||||
final GoogleClusterBuilder? clusterBuilder;
|
final GoogleClusterBuilder? clusterBuilder;
|
||||||
final void Function(List<DataPoint> dataPoints)? onClusterTap;
|
final void Function(List<DataPoint> dataPoints)? onClusterTap;
|
||||||
final EdgeInsets? contentPadding;
|
final EdgeInsets? contentPadding;
|
||||||
final void Function(InteractiveMapController controller)? onMapCreated;
|
final void Function(InteractiveMapController controller)? onMapCreated;
|
||||||
|
final void Function(type.CameraPosition position)? onCameraMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GoogleInteractiveMapState extends State<GoogleInteractiveMap> {
|
class _GoogleInteractiveMapState extends State<GoogleInteractiveMap> {
|
||||||
|
@ -57,7 +59,14 @@ class _GoogleInteractiveMapState extends State<GoogleInteractiveMap> {
|
||||||
const CameraPosition(target: LatLng(0, 0)),
|
const CameraPosition(target: LatLng(0, 0)),
|
||||||
markers: _markers,
|
markers: _markers,
|
||||||
onMapCreated: _onMapCreated,
|
onMapCreated: _onMapCreated,
|
||||||
onCameraMove: _clusterManager.onCameraMove,
|
onCameraMove: (position) {
|
||||||
|
_clusterManager.onCameraMove(position);
|
||||||
|
widget.onCameraMove?.call(type.CameraPosition(
|
||||||
|
center: position.target.toMapCoord(),
|
||||||
|
zoom: position.zoom,
|
||||||
|
rotation: position.bearing,
|
||||||
|
));
|
||||||
|
},
|
||||||
onCameraIdle: _clusterManager.updateMap,
|
onCameraIdle: _clusterManager.updateMap,
|
||||||
padding: widget.contentPadding ?? EdgeInsets.zero,
|
padding: widget.contentPadding ?? EdgeInsets.zero,
|
||||||
);
|
);
|
||||||
|
@ -120,7 +129,7 @@ class _ParentController implements InteractiveMapController {
|
||||||
const _ParentController(this.controller);
|
const _ParentController(this.controller);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setPosition(MapCoord position) {
|
void setPosition(type.MapCoord position) {
|
||||||
controller
|
controller
|
||||||
.animateCamera(CameraUpdate.newLatLngZoom(position.toLatLng(), 10));
|
.animateCamera(CameraUpdate.newLatLngZoom(position.toLatLng(), 10));
|
||||||
}
|
}
|
||||||
|
@ -128,7 +137,7 @@ class _ParentController implements InteractiveMapController {
|
||||||
final GoogleMapController controller;
|
final GoogleMapController controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on MapCoord {
|
extension on type.MapCoord {
|
||||||
LatLng toLatLng() => LatLng(latitude, longitude);
|
LatLng toLatLng() => LatLng(latitude, longitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import 'package:flutter_map_marker_cluster/flutter_map_marker_cluster.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:np_common/object_util.dart';
|
import 'package:np_common/object_util.dart';
|
||||||
import 'package:np_gps_map/src/interactive_map.dart';
|
import 'package:np_gps_map/src/interactive_map.dart';
|
||||||
import 'package:np_gps_map/src/map_coord.dart';
|
import 'package:np_gps_map/src/type.dart';
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
typedef OsmClusterBuilder = Widget Function(
|
typedef OsmClusterBuilder = Widget Function(
|
||||||
|
@ -23,6 +23,7 @@ class OsmInteractiveMap extends StatefulWidget {
|
||||||
this.onClusterTap,
|
this.onClusterTap,
|
||||||
this.contentPadding,
|
this.contentPadding,
|
||||||
this.onMapCreated,
|
this.onMapCreated,
|
||||||
|
this.onCameraMove,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -35,6 +36,7 @@ class OsmInteractiveMap extends StatefulWidget {
|
||||||
final void Function(List<DataPoint> dataPoints)? onClusterTap;
|
final void Function(List<DataPoint> dataPoints)? onClusterTap;
|
||||||
final EdgeInsets? contentPadding;
|
final EdgeInsets? contentPadding;
|
||||||
final void Function(InteractiveMapController controller)? onMapCreated;
|
final void Function(InteractiveMapController controller)? onMapCreated;
|
||||||
|
final void Function(CameraPosition position)? onCameraMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OsmInteractiveMapState extends State<OsmInteractiveMap> {
|
class _OsmInteractiveMapState extends State<OsmInteractiveMap> {
|
||||||
|
@ -47,6 +49,11 @@ class _OsmInteractiveMapState extends State<OsmInteractiveMap> {
|
||||||
widget.onMapCreated?.call(_parentController!);
|
widget.onMapCreated?.call(_parentController!);
|
||||||
_subscriptions.add(_controller.mapEventStream.listen((ev) {
|
_subscriptions.add(_controller.mapEventStream.listen((ev) {
|
||||||
_mapRotationRadSubject.add(ev.camera.rotationRad);
|
_mapRotationRadSubject.add(ev.camera.rotationRad);
|
||||||
|
widget.onCameraMove?.call(CameraPosition(
|
||||||
|
center: ev.camera.center.toMapCoord(),
|
||||||
|
zoom: ev.camera.zoom,
|
||||||
|
rotation: (360 - ev.camera.rotation) % 360,
|
||||||
|
));
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
class MapCoord {
|
|
||||||
const MapCoord(this.latitude, this.longitude);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => "MapCoord {latitude: $latitude, longitude: $longitude}";
|
|
||||||
|
|
||||||
final double latitude;
|
|
||||||
final double longitude;
|
|
||||||
}
|
|
|
@ -1,18 +1,17 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
import 'package:np_gps_map/src/map_coord.dart';
|
import 'package:np_gps_map/src/type.dart' as type;
|
||||||
|
|
||||||
class GoogleGpsMap extends StatelessWidget {
|
class GoogleGpsMap extends StatelessWidget {
|
||||||
const GoogleGpsMap({
|
const GoogleGpsMap({
|
||||||
super.key,
|
super.key,
|
||||||
required this.center,
|
required this.location,
|
||||||
required this.zoom,
|
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final centerLl = LatLng(center.latitude, center.longitude);
|
final center = LatLng(location.center.latitude, location.center.longitude);
|
||||||
return GoogleMap(
|
return GoogleMap(
|
||||||
compassEnabled: false,
|
compassEnabled: false,
|
||||||
mapToolbarEnabled: false,
|
mapToolbarEnabled: false,
|
||||||
|
@ -25,13 +24,14 @@ class GoogleGpsMap extends StatelessWidget {
|
||||||
buildingsEnabled: false,
|
buildingsEnabled: false,
|
||||||
// liteModeEnabled: true,
|
// liteModeEnabled: true,
|
||||||
initialCameraPosition: CameraPosition(
|
initialCameraPosition: CameraPosition(
|
||||||
target: centerLl,
|
target: center,
|
||||||
zoom: zoom,
|
zoom: location.zoom,
|
||||||
|
bearing: location.rotation,
|
||||||
),
|
),
|
||||||
markers: {
|
markers: {
|
||||||
Marker(
|
Marker(
|
||||||
markerId: const MarkerId("at"),
|
markerId: const MarkerId("at"),
|
||||||
position: centerLl,
|
position: center,
|
||||||
// for some reason, GoogleMap's onTap is not triggered if
|
// for some reason, GoogleMap's onTap is not triggered if
|
||||||
// tapped on top of the marker
|
// tapped on top of the marker
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
@ -46,8 +46,7 @@ class GoogleGpsMap extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final MapCoord center;
|
final type.CameraPosition location;
|
||||||
final double zoom;
|
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,30 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:np_gps_map/src/map_coord.dart';
|
import 'package:np_gps_map/src/type.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
class OsmGpsMap extends StatelessWidget {
|
class OsmGpsMap extends StatelessWidget {
|
||||||
const OsmGpsMap({
|
const OsmGpsMap({
|
||||||
super.key,
|
super.key,
|
||||||
required this.center,
|
required this.location,
|
||||||
required this.zoom,
|
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const double pinSize = 48;
|
const double pinSize = 48;
|
||||||
final centerLl = LatLng(center.latitude, center.longitude);
|
final center = LatLng(location.center.latitude, location.center.longitude);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: onTap,
|
||||||
launchUrlString(
|
|
||||||
"https://www.openstreetmap.org/?mlat=${center.latitude}&mlon=${center.longitude}#map=${zoom.toInt()}/${center.latitude}/${center.longitude}",
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
// IgnorePointer is needed to prevent FlutterMap absorbing all pointer
|
// IgnorePointer is needed to prevent FlutterMap absorbing all pointer
|
||||||
// events
|
// events
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: FlutterMap(
|
child: FlutterMap(
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
initialCenter: centerLl,
|
initialCenter: center,
|
||||||
initialZoom: zoom,
|
initialZoom: location.zoom,
|
||||||
|
initialRotation: (360 - location.rotation) % 360,
|
||||||
interactionOptions: const InteractionOptions(
|
interactionOptions: const InteractionOptions(
|
||||||
flags: InteractiveFlag.none,
|
flags: InteractiveFlag.none,
|
||||||
),
|
),
|
||||||
|
@ -40,11 +34,12 @@ class OsmGpsMap extends StatelessWidget {
|
||||||
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||||
),
|
),
|
||||||
MarkerLayer(
|
MarkerLayer(
|
||||||
|
rotate: true,
|
||||||
markers: [
|
markers: [
|
||||||
Marker(
|
Marker(
|
||||||
width: pinSize,
|
width: pinSize,
|
||||||
height: pinSize,
|
height: pinSize,
|
||||||
point: centerLl,
|
point: center,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: const Image(
|
child: const Image(
|
||||||
image: AssetImage(
|
image: AssetImage(
|
||||||
|
@ -62,7 +57,6 @@ class OsmGpsMap extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final MapCoord center;
|
final CameraPosition location;
|
||||||
final double zoom;
|
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
}
|
}
|
||||||
|
|
44
np_gps_map/lib/src/place_picker.dart
Normal file
44
np_gps_map/lib/src/place_picker.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:np_gps_map/src/interactive_map.dart';
|
||||||
|
import 'package:np_gps_map/src/type.dart';
|
||||||
|
|
||||||
|
class PlacePickerView extends StatelessWidget {
|
||||||
|
const PlacePickerView({
|
||||||
|
super.key,
|
||||||
|
required this.providerHint,
|
||||||
|
this.initialPosition,
|
||||||
|
this.initialZoom,
|
||||||
|
this.contentPadding,
|
||||||
|
this.onCameraMove,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
InteractiveMap(
|
||||||
|
providerHint: providerHint,
|
||||||
|
initialPosition: initialPosition,
|
||||||
|
initialZoom: initialZoom,
|
||||||
|
contentPadding: contentPadding,
|
||||||
|
onCameraMove: onCameraMove,
|
||||||
|
),
|
||||||
|
Positioned.fill(
|
||||||
|
child: Transform.translate(
|
||||||
|
// 48(height) / 2
|
||||||
|
offset: const Offset(0, -24),
|
||||||
|
child: Center(
|
||||||
|
child: Image.asset("packages/np_gps_map/assets/gps_map_pin.png"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final GpsMapProvider providerHint;
|
||||||
|
final MapCoord? initialPosition;
|
||||||
|
final double? initialZoom;
|
||||||
|
final EdgeInsets? contentPadding;
|
||||||
|
final void Function(CameraPosition position)? onCameraMove;
|
||||||
|
}
|
|
@ -1,23 +1,16 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:np_gps_map/src/map_coord.dart';
|
|
||||||
import 'package:np_gps_map/src/native/google_gps_map.dart'
|
import 'package:np_gps_map/src/native/google_gps_map.dart'
|
||||||
if (dart.library.html) 'package:np_gps_map/src/web/google_gps_map.dart';
|
if (dart.library.html) 'package:np_gps_map/src/web/google_gps_map.dart';
|
||||||
import 'package:np_gps_map/src/osm_gps_map.dart';
|
import 'package:np_gps_map/src/osm_gps_map.dart';
|
||||||
|
import 'package:np_gps_map/src/type.dart';
|
||||||
import 'package:np_gps_map/src/util.dart';
|
import 'package:np_gps_map/src/util.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
|
||||||
enum GpsMapProvider {
|
class StaticMap extends StatelessWidget {
|
||||||
google,
|
const StaticMap({
|
||||||
osm,
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GpsMap extends StatelessWidget {
|
|
||||||
const GpsMap({
|
|
||||||
super.key,
|
super.key,
|
||||||
required this.providerHint,
|
required this.providerHint,
|
||||||
required this.center,
|
required this.location,
|
||||||
required this.zoom,
|
|
||||||
this.onTap,
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -26,14 +19,12 @@ class GpsMap extends StatelessWidget {
|
||||||
if (providerHint == GpsMapProvider.osm ||
|
if (providerHint == GpsMapProvider.osm ||
|
||||||
(getRawPlatform() == NpPlatform.android && !isNewGMapsRenderer())) {
|
(getRawPlatform() == NpPlatform.android && !isNewGMapsRenderer())) {
|
||||||
return OsmGpsMap(
|
return OsmGpsMap(
|
||||||
center: center,
|
location: location,
|
||||||
zoom: zoom,
|
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return GoogleGpsMap(
|
return GoogleGpsMap(
|
||||||
center: center,
|
location: location,
|
||||||
zoom: zoom,
|
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,8 +34,6 @@ class GpsMap extends StatelessWidget {
|
||||||
/// actual choice may be different depending on the runtime environment
|
/// actual choice may be different depending on the runtime environment
|
||||||
final GpsMapProvider providerHint;
|
final GpsMapProvider providerHint;
|
||||||
|
|
||||||
/// A pair of latitude and longitude coordinates, stored as degrees
|
final CameraPosition location;
|
||||||
final MapCoord center;
|
|
||||||
final double zoom;
|
|
||||||
final void Function()? onTap;
|
final void Function()? onTap;
|
||||||
}
|
}
|
80
np_gps_map/lib/src/type.dart
Normal file
80
np_gps_map/lib/src/type.dart
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart' as gmap;
|
||||||
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:np_common/type.dart';
|
||||||
|
|
||||||
|
enum GpsMapProvider {
|
||||||
|
google,
|
||||||
|
osm,
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pair of latitude and longitude coordinates, stored as degrees
|
||||||
|
class MapCoord {
|
||||||
|
const MapCoord(this.latitude, this.longitude);
|
||||||
|
|
||||||
|
MapCoord.fromJson(JsonObj json)
|
||||||
|
: latitude = json["lat"],
|
||||||
|
longitude = json["lng"];
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "MapCoord {latitude: $latitude, longitude: $longitude}";
|
||||||
|
|
||||||
|
JsonObj toJson() => {
|
||||||
|
"lat": latitude,
|
||||||
|
"lng": longitude,
|
||||||
|
};
|
||||||
|
|
||||||
|
final double latitude;
|
||||||
|
final double longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension GLatLngExtension on gmap.LatLng {
|
||||||
|
MapCoord toMapCoord() => MapCoord(latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LatLngExtension on LatLng {
|
||||||
|
MapCoord toMapCoord() => MapCoord(latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CameraPosition with EquatableMixin {
|
||||||
|
const CameraPosition({
|
||||||
|
required this.center,
|
||||||
|
required this.zoom,
|
||||||
|
this.rotation = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CameraPosition.fromJson(JsonObj json) {
|
||||||
|
return CameraPosition(
|
||||||
|
center: MapCoord.fromJson(json["center"]),
|
||||||
|
zoom: json["zoom"],
|
||||||
|
rotation: json["rotation"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "CameraPosition {"
|
||||||
|
"center: $center, "
|
||||||
|
"zoom: $zoom, "
|
||||||
|
"rotation: $rotation, "
|
||||||
|
"}";
|
||||||
|
|
||||||
|
JsonObj toJson() {
|
||||||
|
return {
|
||||||
|
"center": center.toJson(),
|
||||||
|
"zoom": zoom,
|
||||||
|
"rotation": rotation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [center, zoom, rotation];
|
||||||
|
|
||||||
|
final MapCoord center;
|
||||||
|
final double zoom;
|
||||||
|
// The camera's bearing in degrees, measured clockwise from north.
|
||||||
|
//
|
||||||
|
// A bearing of 0.0, the default, means the camera points north.
|
||||||
|
// A bearing of 90.0 means the camera points east.
|
||||||
|
final double rotation;
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ environment:
|
||||||
flutter: ">=3.19.0"
|
flutter: ">=3.19.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
equatable: ^2.0.5
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_map: ^6.1.0
|
flutter_map: ^6.1.0
|
||||||
|
@ -25,7 +26,6 @@ dependencies:
|
||||||
np_platform_util:
|
np_platform_util:
|
||||||
path: ../np_platform_util
|
path: ../np_platform_util
|
||||||
rxdart: ^0.27.7
|
rxdart: ^0.27.7
|
||||||
url_launcher: ^6.1.11
|
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
google_maps_flutter_android: 2.7.0
|
google_maps_flutter_android: 2.7.0
|
||||||
|
|
Loading…
Reference in a new issue