Refactor: extract map

This commit is contained in:
Ming Ming 2023-09-13 00:29:44 +08:00
parent b4cd90d6d4
commit 436a760950
36 changed files with 529 additions and 276 deletions

View file

@ -120,7 +120,6 @@ dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
// fix crash on sdk33, need investigation
implementation "androidx.window:window:1.0.0"
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.nkming.nc_photos.np_android_core:np_android_core'
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.3"
}

View file

@ -4,10 +4,7 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.annotation.NonNull
import com.google.android.gms.maps.MapsInitializer
import com.google.android.gms.maps.OnMapsSdkInitializedCallback
import com.nkming.nc_photos.np_android_core.UriUtil
import com.nkming.nc_photos.np_android_core.logD
import com.nkming.nc_photos.np_android_core.logE
import com.nkming.nc_photos.np_android_core.logI
import com.nkming.nc_photos.np_platform_image_processor.NpPlatformImageProcessorPlugin
@ -18,118 +15,95 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.net.URLEncoder
class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler,
OnMapsSdkInitializedCallback {
companion object {
private const val METHOD_CHANNEL = "com.nkming.nc_photos/activity"
class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler {
companion object {
private const val METHOD_CHANNEL = "com.nkming.nc_photos/activity"
private const val TAG = "MainActivity"
}
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT) {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Initial route: $route")
_initialRoute = route
}
MapsInitializer.initialize(
applicationContext, MapsInitializer.Renderer.LATEST, this
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT) {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Initial route: $route")
_initialRoute = route
}
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
SelfSignedCertChannelHandler.CHANNEL
).setMethodCallHandler(
SelfSignedCertChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
ShareChannelHandler.CHANNEL
).setMethodCallHandler(
ShareChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL
).setMethodCallHandler(this)
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
SelfSignedCertChannelHandler.CHANNEL
).setMethodCallHandler(
SelfSignedCertChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
ShareChannelHandler.CHANNEL
).setMethodCallHandler(
ShareChannelHandler(this)
)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger, METHOD_CHANNEL
).setMethodCallHandler(this)
EventChannel(
flutterEngine.dartExecutor.binaryMessenger,
DownloadEventCancelChannelHandler.CHANNEL
).setStreamHandler(
DownloadEventCancelChannelHandler(this)
)
}
EventChannel(
flutterEngine.dartExecutor.binaryMessenger,
DownloadEventCancelChannelHandler.CHANNEL
).setStreamHandler(
DownloadEventCancelChannelHandler(this)
)
}
override fun onNewIntent(intent: Intent) {
when (intent.action) {
NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT -> {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Navigate to route: $route")
flutterEngine?.navigationChannel?.pushRoute(route)
}
override fun onNewIntent(intent: Intent) {
when (intent.action) {
NpPlatformImageProcessorPlugin.ACTION_SHOW_IMAGE_PROCESSOR_RESULT -> {
val route = getRouteFromImageProcessorResult(intent) ?: return
logI(TAG, "Navigate to route: $route")
flutterEngine?.navigationChannel?.pushRoute(route)
}
else -> {
super.onNewIntent(intent)
}
}
}
else -> {
super.onNewIntent(intent)
}
}
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"consumeInitialRoute" -> {
result.success(_initialRoute)
_initialRoute = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"consumeInitialRoute" -> {
result.success(_initialRoute)
_initialRoute = null
}
"isNewGMapsRenderer" -> {
result.success(_isNewGMapsRenderer)
}
else -> result.notImplemented()
}
}
else -> result.notImplemented()
}
}
private fun getRouteFromImageProcessorResult(intent: Intent): String? {
val resultUri = intent.getParcelableExtra<Uri>(
NpPlatformImageProcessorPlugin.EXTRA_IMAGE_RESULT_URI
)
if (resultUri == null) {
logE(TAG, "Image result uri == null")
return null
}
return if (resultUri.scheme?.startsWith("http") == true) {
// remote uri
val encodedUrl = URLEncoder.encode(resultUri.toString(), "utf-8")
"/result-viewer?url=$encodedUrl"
} else {
val filename = UriUtil.resolveFilename(this, resultUri)?.let {
URLEncoder.encode(it, Charsets.UTF_8.toString())
}
StringBuilder().apply {
append("/enhanced-photo-browser?")
if (filename != null) append("filename=$filename")
}.toString()
}
}
override fun onMapsSdkInitialized(renderer: MapsInitializer.Renderer) {
_isNewGMapsRenderer = when (renderer) {
MapsInitializer.Renderer.LATEST -> {
logD(TAG, "Using new map renderer")
true
}
MapsInitializer.Renderer.LEGACY -> {
logD(TAG, "Using legacy map renderer")
false
}
}
}
private fun getRouteFromImageProcessorResult(intent: Intent): String? {
val resultUri = intent.getParcelableExtra<Uri>(
NpPlatformImageProcessorPlugin.EXTRA_IMAGE_RESULT_URI
)
if (resultUri == null) {
logE(TAG, "Image result uri == null")
return null
}
return if (resultUri.scheme?.startsWith("http") == true) {
// remote uri
val encodedUrl = URLEncoder.encode(resultUri.toString(), "utf-8")
"/result-viewer?url=$encodedUrl"
} else {
val filename = UriUtil.resolveFilename(this, resultUri)?.let {
URLEncoder.encode(it, Charsets.UTF_8.toString())
}
StringBuilder().apply {
append("/enhanced-photo-browser?")
if (filename != null) append("filename=$filename")
}.toString()
}
}
private var _initialRoute: String? = null
private var _isNewGMapsRenderer = false
private var _initialRoute: String? = null
}

View file

@ -37,13 +37,13 @@ import 'package:nc_photos/entity/tag/data_source.dart';
import 'package:nc_photos/entity/tagged_file.dart';
import 'package:nc_photos/entity/tagged_file/data_source.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/activity.dart';
import 'package:nc_photos/mobile/android/android_info.dart';
import 'package:nc_photos/mobile/platform.dart'
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
import 'package:nc_photos/platform/features.dart' as features;
import 'package:nc_photos/touch_manager.dart';
import 'package:np_gps_map/np_gps_map.dart';
import 'package:np_log/np_log.dart' as np_log;
import 'package:np_platform_util/np_platform_util.dart';
import 'package:visibility_detector/visibility_detector.dart';
@ -56,8 +56,6 @@ enum InitIsolateType {
flutterIsolate,
}
bool isNewGMapsRenderer() => _isNewGMapsRenderer;
Future<void> init(InitIsolateType isolateType) async {
if (_hasInitedInThisIsolate) {
_log.warning("[init] Already initialized in this isolate");
@ -79,16 +77,7 @@ Future<void> init(InitIsolateType isolateType) async {
}
await _initDiContainer(isolateType);
_initVisibilityDetector();
if (getRawPlatform() == NpPlatform.android) {
if (isolateType == InitIsolateType.main) {
try {
_isNewGMapsRenderer = await Activity.isNewGMapsRenderer();
} catch (e, stackTrace) {
_log.severe("[init] Failed while isNewGMapsRenderer", e, stackTrace);
}
}
}
GpsMap.init();
_hasInitedInThisIsolate = true;
}
@ -237,4 +226,3 @@ Future<sql.SqliteDb> _createDb(InitIsolateType isolateType) async {
final _log = Logger("app_init");
var _hasInitedInThisIsolate = false;
var _isNewGMapsRenderer = false;

View file

@ -7,8 +7,8 @@ import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/language_util.dart' as language_util;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/size.dart';
import 'package:nc_photos/widget/gps_map.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_gps_map/np_gps_map.dart';
import 'package:rxdart/rxdart.dart';
part 'pref_controller.g.dart';
@ -256,7 +256,7 @@ class PrefController {
late final _isViewerForceRotationController =
BehaviorSubject.seeded(_c.pref.isViewerForceRotationOr(false));
late final _gpsMapProviderController = BehaviorSubject.seeded(
GpsMapProvider.fromValue(_c.pref.getGpsMapProviderOr(0)));
GpsMapProvider.values[_c.pref.getGpsMapProviderOr(0)]);
late final _isAlbumBrowserShowDateController =
BehaviorSubject.seeded(_c.pref.isAlbumBrowserShowDateOr(false));
late final _isDoubleTapExitController =

12
app/lib/gps_map_util.dart Normal file
View file

@ -0,0 +1,12 @@
import 'package:np_gps_map/np_gps_map.dart';
extension GpsMapProviderExtension on GpsMapProvider {
String toUserString() {
switch (this) {
case GpsMapProvider.google:
return "Google Maps";
case GpsMapProvider.osm:
return "OpenStreetMap";
}
}
}

View file

@ -1,12 +1,8 @@
import 'package:flutter/services.dart';
import 'package:np_async/np_async.dart';
class Activity {
static Future<String?> consumeInitialRoute() =>
_methodChannel.invokeMethod("consumeInitialRoute");
static Future<bool> isNewGMapsRenderer() =>
_methodChannel.invokeMethod<bool>("isNewGMapsRenderer").notNull();
static const _methodChannel = MethodChannel("com.nkming.nc_photos/activity");
}

View file

@ -1,5 +1,4 @@
export 'db_util.dart';
export 'download.dart';
export 'file_saver.dart';
export 'google_gps_map.dart';
export 'notification.dart';

View file

@ -1,5 +1,4 @@
export 'db_util.dart';
export 'download.dart';
export 'file_saver.dart';
export 'google_gps_map.dart';
export 'notification.dart';

View file

@ -1,126 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:nc_photos/app_init.dart' as app_init;
import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/mobile/platform.dart'
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
import 'package:nc_photos/url_launcher_util.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:tuple/tuple.dart';
enum GpsMapProvider {
// the order must not be changed
google,
osm,
;
static GpsMapProvider fromValue(int value) => GpsMapProvider.values[value];
String toUserString() {
switch (this) {
case GpsMapProvider.google:
return "Google Maps";
case GpsMapProvider.osm:
return "OpenStreetMap";
}
}
}
class GpsMap extends StatelessWidget {
const GpsMap({
Key? key,
required this.center,
required this.zoom,
this.onTap,
}) : super(key: key);
@override
build(BuildContext context) {
if (GpsMapProvider.values[Pref().getGpsMapProviderOr(0)] ==
GpsMapProvider.osm ||
(getRawPlatform() == NpPlatform.android &&
!app_init.isNewGMapsRenderer())) {
return _OsmGpsMap(
center: center,
zoom: zoom,
onTap: onTap,
);
} else {
return _GoogleGpsMap(
center: center,
zoom: zoom,
onTap: onTap,
);
}
}
/// A pair of latitude and longitude coordinates, stored as degrees
final Tuple2<double, double> center;
final double zoom;
final void Function()? onTap;
}
typedef _GoogleGpsMap = platform.GoogleGpsMap;
class _OsmGpsMap extends StatelessWidget {
const _OsmGpsMap({
Key? key,
required this.center,
required this.zoom,
this.onTap,
}) : super(key: key);
@override
build(BuildContext context) {
const double pinSize = 48;
final centerLl = LatLng(center.item1, center.item2);
return GestureDetector(
onTap: () {
launch(
"https://www.openstreetmap.org/?mlat=${center.item1}&mlon=${center.item2}#map=${zoom.toInt()}/${center.item1}/${center.item2}");
},
behavior: HitTestBehavior.opaque,
// IgnorePointer is needed to prevent FlutterMap absorbing all pointer
// events
child: IgnorePointer(
child: FlutterMap(
options: MapOptions(
center: centerLl,
zoom: zoom,
allowPanning: false,
enableScrollWheel: false,
interactiveFlags: InteractiveFlag.none,
),
nonRotatedChildren: [
AttributionWidget.defaultWidget(
source: "OpenStreetMap contributors",
),
],
layers: [
TileLayerOptions(
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
),
MarkerLayerOptions(
markers: [
Marker(
width: pinSize,
height: pinSize,
point: centerLl,
anchorPos: AnchorPos.align(AnchorAlign.top),
builder: (context) => const Image(
image: AssetImage("assets/gps_map_pin.png"),
),
),
],
),
],
),
),
);
}
final Tuple2<double, double> center;
final double zoom;
final void Function()? onTap;
}

View file

@ -7,12 +7,13 @@ import 'package:nc_photos/bloc_util.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/exception_event.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/gps_map_util.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/widget/fancy_option_picker.dart';
import 'package:nc_photos/widget/gps_map.dart';
import 'package:nc_photos/widget/page_visibility_mixin.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_gps_map/np_gps_map.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:np_ui/np_ui.dart';
import 'package:screen_brightness/screen_brightness.dart';

View file

@ -9,6 +9,7 @@ import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/controller/account_controller.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/collection.dart';
@ -23,18 +24,19 @@ import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/platform/features.dart' as features;
import 'package:nc_photos/set_as_handler.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/stream_util.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
import 'package:nc_photos/use_case/list_file_tag.dart';
import 'package:nc_photos/use_case/update_property.dart';
import 'package:nc_photos/widget/about_geocoding_dialog.dart';
import 'package:nc_photos/widget/gps_map.dart';
import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart';
import 'package:nc_photos/widget/list_tile_center_leading.dart';
import 'package:nc_photos/widget/photo_date_time_edit_dialog.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/or_null.dart';
import 'package:np_geocoder/np_geocoder.dart';
import 'package:np_gps_map/np_gps_map.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:np_string/np_string.dart';
import 'package:np_ui/np_ui.dart';
@ -300,10 +302,14 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
duration: k.animationDurationNormal,
child: SizedBox(
height: 256,
child: GpsMap(
center: _gps!,
zoom: 16,
onTap: _onMapTap,
child: ValueStreamBuilder<GpsMapProvider>(
stream: context.read<PrefController>().gpsMapProvider,
builder: (context, gpsMapProvider) => GpsMap(
providerHint: gpsMapProvider.requireData,
center: _gps!,
zoom: 16,
onTap: _onMapTap,
),
),
),
),

View file

@ -594,7 +594,7 @@ packages:
source: sdk
version: "0.0.0"
flutter_map:
dependency: "direct main"
dependency: transitive
description:
name: flutter_map
sha256: "09010e452bcd8c57ade1b936b79643c4fd599f93b00d1696630f0b919b6f374a"
@ -799,7 +799,7 @@ packages:
source: hosted
version: "4.1.0"
latlong2:
dependency: "direct main"
dependency: transitive
description:
name: latlong2
sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0"
@ -990,6 +990,13 @@ packages:
relative: true
source: path
version: "0.0.1"
np_gps_map:
dependency: "direct main"
description:
path: "../np_gps_map"
relative: true
source: path
version: "0.0.1"
np_lints:
dependency: "direct dev"
description:

View file

@ -69,7 +69,6 @@ dependencies:
flutter_cache_manager: any
flutter_colorpicker: ^1.0.3
flutter_isolate: ^2.0.4
flutter_map: ^2.2.0
flutter_staggered_grid_view:
git:
url: https://gitlab.com/nc-photos/flutter_staggered_grid_view
@ -85,7 +84,6 @@ dependencies:
path: library
intl: ^0.17.0
kiwi: ^4.1.0
latlong2: any
logging: ^1.1.1
memory_info: ^0.0.3
mime: ^1.0.4
@ -105,6 +103,8 @@ dependencies:
path: ../np_collection
np_geocoder:
path: ../np_geocoder
np_gps_map:
path: ../np_gps_map
np_log:
path: ../np_log
np_math:

30
np_gps_map/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

30
np_gps_map/.metadata Normal file
View file

@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
channel: stable
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
- platform: android
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View file

@ -0,0 +1 @@
include: package:np_lints/np.yaml

9
np_gps_map/android/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View file

@ -0,0 +1,54 @@
group 'com.nkming.nc_photos.np_gps_map'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.8.20'
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace 'com.nkming.nc_photos.np_gps_map'
compileSdk 33
defaultConfig {
minSdk 21
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "androidx.annotation:annotation:1.6.0"
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'com.nkming.nc_photos.np_android_core:np_android_core'
}

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -0,0 +1,2 @@
rootProject.name = 'np_gps_map'
includeBuild '../../np_android_core'

View file

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nkming.nc_photos.np_gps_map">
</manifest>

View file

@ -0,0 +1,26 @@
package com.nkming.nc_photos.np_gps_map
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
internal class GpsMapChannelHandler : MethodChannel.MethodCallHandler {
companion object {
const val METHOD_CHANNEL = "${K.LIB_ID}/gps_map_method"
private const val TAG = "GpsMapChannelHandler"
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"isNewGMapsRenderer" -> {
result.success(isNewGMapsRenderer)
}
else -> {
result.notImplemented()
}
}
}
var isNewGMapsRenderer = false
}

View file

@ -0,0 +1,7 @@
package com.nkming.nc_photos.np_gps_map
internal interface K {
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_gps_map"
}
}

View file

@ -0,0 +1,54 @@
package com.nkming.nc_photos.np_gps_map
import androidx.annotation.NonNull
import com.google.android.gms.maps.MapsInitializer
import com.google.android.gms.maps.OnMapsSdkInitializedCallback
import com.nkming.nc_photos.np_android_core.logD
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
class NpGpsMapPlugin : FlutterPlugin {
companion object {
private const val TAG = "NpGpsMapPlugin"
}
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
handler = GpsMapChannelHandler()
methodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
GpsMapChannelHandler.METHOD_CHANNEL
)
methodChannel.setMethodCallHandler(handler)
MapsInitializer.initialize(
flutterPluginBinding.applicationContext,
MapsInitializer.Renderer.LATEST, mapCallback
)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
methodChannel.setMethodCallHandler(null)
}
private lateinit var methodChannel: MethodChannel
private lateinit var handler: GpsMapChannelHandler
private val mapCallback = OnMapsSdkInitializedCallback {
handler.isNewGMapsRenderer = when (it) {
MapsInitializer.Renderer.LATEST -> {
logD(TAG, "Using new map renderer")
true
}
MapsInitializer.Renderer.LEGACY -> {
logD(TAG, "Using legacy map renderer")
false
}
}
}
}

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 5 KiB

View file

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,3 @@
library np_gps_map;
export 'src/gps_map.dart';

View file

@ -0,0 +1,58 @@
import 'package:flutter/material.dart';
import 'package:np_gps_map/src/native.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';
import 'package:np_gps_map/src/osm_gps_map.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:tuple/tuple.dart';
enum GpsMapProvider {
google,
osm,
;
}
class GpsMap extends StatelessWidget {
const GpsMap({
super.key,
required this.providerHint,
required this.center,
required this.zoom,
this.onTap,
});
static void init() {
if (getRawPlatform() == NpPlatform.android) {
Native.isNewGMapsRenderer().then((value) => _isNewGMapsRenderer = value);
}
}
@override
Widget build(BuildContext context) {
if (providerHint == GpsMapProvider.osm ||
(getRawPlatform() == NpPlatform.android && !_isNewGMapsRenderer)) {
return OsmGpsMap(
center: center,
zoom: zoom,
onTap: onTap,
);
} else {
return GoogleGpsMap(
center: center,
zoom: zoom,
onTap: onTap,
);
}
}
/// The backend to provide the actual map. This works as a hint only, the
/// actual choice may be different depending on the runtime environment
final GpsMapProvider providerHint;
/// A pair of latitude and longitude coordinates, stored as degrees
final Tuple2<double, double> center;
final double zoom;
final void Function()? onTap;
static bool _isNewGMapsRenderer = false;
}

View file

@ -0,0 +1 @@
const libId = "com.nkming.nc_photos.np_gps_map";

View file

@ -0,0 +1,10 @@
import 'package:flutter/services.dart';
import 'package:np_async/np_async.dart';
import 'package:np_gps_map/src/k.dart' as k;
class Native {
static Future<bool> isNewGMapsRenderer() =>
_methodChannel.invokeMethod<bool>("isNewGMapsRenderer").notNull();
static const _methodChannel = MethodChannel("${k.libId}/gps_map_method");
}

View file

@ -4,14 +4,14 @@ import 'package:tuple/tuple.dart';
class GoogleGpsMap extends StatelessWidget {
const GoogleGpsMap({
Key? key,
super.key,
required this.center,
required this.zoom,
this.onTap,
}) : super(key: key);
});
@override
build(BuildContext context) {
Widget build(BuildContext context) {
final centerLl = LatLng(center.item1, center.item2);
return GoogleMap(
compassEnabled: false,

View file

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:tuple/tuple.dart';
import 'package:url_launcher/url_launcher_string.dart';
class OsmGpsMap extends StatelessWidget {
const OsmGpsMap({
super.key,
required this.center,
required this.zoom,
this.onTap,
});
@override
Widget build(BuildContext context) {
const double pinSize = 48;
final centerLl = LatLng(center.item1, center.item2);
return GestureDetector(
onTap: () {
launchUrlString(
"https://www.openstreetmap.org/?mlat=${center.item1}&mlon=${center.item2}#map=${zoom.toInt()}/${center.item1}/${center.item2}",
mode: LaunchMode.externalApplication,
);
},
behavior: HitTestBehavior.opaque,
// IgnorePointer is needed to prevent FlutterMap absorbing all pointer
// events
child: IgnorePointer(
child: FlutterMap(
options: MapOptions(
center: centerLl,
zoom: zoom,
allowPanning: false,
enableScrollWheel: false,
interactiveFlags: InteractiveFlag.none,
),
nonRotatedChildren: [
AttributionWidget.defaultWidget(
source: "OpenStreetMap contributors",
),
],
layers: [
TileLayerOptions(
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
),
MarkerLayerOptions(
markers: [
Marker(
width: pinSize,
height: pinSize,
point: centerLl,
anchorPos: AnchorPos.align(AnchorAlign.top),
builder: (_) => const Image(
image: AssetImage(
"packages/np_gps_map/assets/gps_map_pin.png"),
),
),
],
),
],
),
),
);
}
final Tuple2<double, double> center;
final double zoom;
final void Function()? onTap;
}

View file

@ -2,20 +2,20 @@
import 'dart:html';
import 'package:flutter/widgets.dart';
import 'package:nc_photos/mobile/ui_hack.dart' if (dart.library.html) 'dart:ui'
import 'package:np_gps_map/src/ui_hack.dart' if (dart.library.html) 'dart:ui'
as ui;
import 'package:tuple/tuple.dart';
class GoogleGpsMap extends StatefulWidget {
const GoogleGpsMap({
Key? key,
super.key,
required this.center,
required this.zoom,
this.onTap,
}) : super(key: key);
});
@override
createState() => _GoogleGpsMapState();
State<StatefulWidget> createState() => _GoogleGpsMapState();
final Tuple2<double, double> center;
final double zoom;
@ -24,18 +24,18 @@ class GoogleGpsMap extends StatefulWidget {
class _GoogleGpsMapState extends State<GoogleGpsMap> {
@override
initState() {
void initState() {
super.initState();
final iframe = IFrameElement()
..src = "https://www.google.com/maps/embed/v1/place?key=$_apiKey"
"&q=${widget.center.item1},${widget.center.item2}"
"&zoom=${widget.zoom}"
..style.border = "none";
ui.platformViewRegistry.registerViewFactory(viewType, (viewId) => iframe);
ui.platformViewRegistry.registerViewFactory(viewType, (_) => iframe);
}
@override
build(BuildContext context) {
Widget build(BuildContext context) {
return HtmlElementView(
viewType: viewType,
);

35
np_gps_map/pubspec.yaml Normal file
View file

@ -0,0 +1,35 @@
name: np_gps_map
description: A new Flutter package project.
version: 0.0.1
homepage:
publish_to: none
environment:
sdk: '>=2.19.6 <3.0.0'
flutter: ">=3.3.0"
dependencies:
flutter:
sdk: flutter
flutter_map: ^2.2.0
google_maps_flutter: ^2.2.8
latlong2: any
np_async:
path: ../np_async
np_platform_util:
path: ../np_platform_util
tuple: ^2.0.1
url_launcher: ^6.1.11
dev_dependencies:
np_lints:
path: ../np_lints
flutter:
plugin:
platforms:
android:
package: com.nkming.nc_photos.np_gps_map
pluginClass: NpGpsMapPlugin
assets:
- assets/