diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle index 8fd71ff8..5448eb39 100644 --- a/app/android/app/build.gradle +++ b/app/android/app/build.gradle @@ -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" } diff --git a/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt b/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt index bd38ab5b..fed93a58 100644 --- a/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt +++ b/app/android/app/src/main/kotlin/com/nkming/nc_photos/MainActivity.kt @@ -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( + 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( - 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 } diff --git a/app/lib/app_init.dart b/app/lib/app_init.dart index c2b1b4c9..6eeafd41 100644 --- a/app/lib/app_init.dart +++ b/app/lib/app_init.dart @@ -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 init(InitIsolateType isolateType) async { if (_hasInitedInThisIsolate) { _log.warning("[init] Already initialized in this isolate"); @@ -79,16 +77,7 @@ Future 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 _createDb(InitIsolateType isolateType) async { final _log = Logger("app_init"); var _hasInitedInThisIsolate = false; -var _isNewGMapsRenderer = false; diff --git a/app/lib/controller/pref_controller.dart b/app/lib/controller/pref_controller.dart index 697327c2..ed4ad931 100644 --- a/app/lib/controller/pref_controller.dart +++ b/app/lib/controller/pref_controller.dart @@ -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 = diff --git a/app/lib/gps_map_util.dart b/app/lib/gps_map_util.dart new file mode 100644 index 00000000..467679f2 --- /dev/null +++ b/app/lib/gps_map_util.dart @@ -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"; + } + } +} diff --git a/app/lib/mobile/android/activity.dart b/app/lib/mobile/android/activity.dart index 092102ad..16388ab3 100644 --- a/app/lib/mobile/android/activity.dart +++ b/app/lib/mobile/android/activity.dart @@ -1,12 +1,8 @@ import 'package:flutter/services.dart'; -import 'package:np_async/np_async.dart'; class Activity { static Future consumeInitialRoute() => _methodChannel.invokeMethod("consumeInitialRoute"); - static Future isNewGMapsRenderer() => - _methodChannel.invokeMethod("isNewGMapsRenderer").notNull(); - static const _methodChannel = MethodChannel("com.nkming.nc_photos/activity"); } diff --git a/app/lib/mobile/platform.dart b/app/lib/mobile/platform.dart index a7be646c..0c76f538 100644 --- a/app/lib/mobile/platform.dart +++ b/app/lib/mobile/platform.dart @@ -1,5 +1,4 @@ export 'db_util.dart'; export 'download.dart'; export 'file_saver.dart'; -export 'google_gps_map.dart'; export 'notification.dart'; diff --git a/app/lib/web/platform.dart b/app/lib/web/platform.dart index a7be646c..0c76f538 100644 --- a/app/lib/web/platform.dart +++ b/app/lib/web/platform.dart @@ -1,5 +1,4 @@ export 'db_util.dart'; export 'download.dart'; export 'file_saver.dart'; -export 'google_gps_map.dart'; export 'notification.dart'; diff --git a/app/lib/widget/gps_map.dart b/app/lib/widget/gps_map.dart deleted file mode 100644 index 74704b75..00000000 --- a/app/lib/widget/gps_map.dart +++ /dev/null @@ -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 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 center; - final double zoom; - final void Function()? onTap; -} diff --git a/app/lib/widget/settings/viewer_settings.dart b/app/lib/widget/settings/viewer_settings.dart index c2c595e8..08e2c31d 100644 --- a/app/lib/widget/settings/viewer_settings.dart +++ b/app/lib/widget/settings/viewer_settings.dart @@ -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'; diff --git a/app/lib/widget/viewer_detail_pane.dart b/app/lib/widget/viewer_detail_pane.dart index 890448cd..21955b3e 100644 --- a/app/lib/widget/viewer_detail_pane.dart +++ b/app/lib/widget/viewer_detail_pane.dart @@ -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 { duration: k.animationDurationNormal, child: SizedBox( height: 256, - child: GpsMap( - center: _gps!, - zoom: 16, - onTap: _onMapTap, + child: ValueStreamBuilder( + stream: context.read().gpsMapProvider, + builder: (context, gpsMapProvider) => GpsMap( + providerHint: gpsMapProvider.requireData, + center: _gps!, + zoom: 16, + onTap: _onMapTap, + ), ), ), ), diff --git a/app/pubspec.lock b/app/pubspec.lock index 8b71053c..7c00c538 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -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: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index e125558d..69b50be2 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -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: diff --git a/np_gps_map/.gitignore b/np_gps_map/.gitignore new file mode 100644 index 00000000..96486fd9 --- /dev/null +++ b/np_gps_map/.gitignore @@ -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/ diff --git a/np_gps_map/.metadata b/np_gps_map/.metadata new file mode 100644 index 00000000..b0ed2b59 --- /dev/null +++ b/np_gps_map/.metadata @@ -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' diff --git a/np_gps_map/analysis_options.yaml b/np_gps_map/analysis_options.yaml new file mode 100644 index 00000000..f92d2567 --- /dev/null +++ b/np_gps_map/analysis_options.yaml @@ -0,0 +1 @@ +include: package:np_lints/np.yaml diff --git a/np_gps_map/android/.gitignore b/np_gps_map/android/.gitignore new file mode 100644 index 00000000..161bdcda --- /dev/null +++ b/np_gps_map/android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.cxx diff --git a/np_gps_map/android/build.gradle b/np_gps_map/android/build.gradle new file mode 100644 index 00000000..4c52cabd --- /dev/null +++ b/np_gps_map/android/build.gradle @@ -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' +} diff --git a/np_gps_map/android/gradle/wrapper/gradle-wrapper.properties b/np_gps_map/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..98debb84 --- /dev/null +++ b/np_gps_map/android/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/np_gps_map/android/settings.gradle b/np_gps_map/android/settings.gradle new file mode 100644 index 00000000..840089c6 --- /dev/null +++ b/np_gps_map/android/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'np_gps_map' +includeBuild '../../np_android_core' diff --git a/np_gps_map/android/src/main/AndroidManifest.xml b/np_gps_map/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7c8c2743 --- /dev/null +++ b/np_gps_map/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/GpsMapChannelHandler.kt b/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/GpsMapChannelHandler.kt new file mode 100644 index 00000000..052452c2 --- /dev/null +++ b/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/GpsMapChannelHandler.kt @@ -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 +} diff --git a/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/K.kt b/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/K.kt new file mode 100644 index 00000000..a8a3f161 --- /dev/null +++ b/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/K.kt @@ -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" + } +} diff --git a/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/NpGpsMapPlugin.kt b/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/NpGpsMapPlugin.kt new file mode 100644 index 00000000..7981285a --- /dev/null +++ b/np_gps_map/android/src/main/kotlin/com/nkming/nc_photos/np_gps_map/NpGpsMapPlugin.kt @@ -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 + } + } + + } +} diff --git a/app/assets/2.0x/gps_map_pin.png b/np_gps_map/assets/2.0x/gps_map_pin.png similarity index 100% rename from app/assets/2.0x/gps_map_pin.png rename to np_gps_map/assets/2.0x/gps_map_pin.png diff --git a/app/assets/3.0x/gps_map_pin.png b/np_gps_map/assets/3.0x/gps_map_pin.png similarity index 100% rename from app/assets/3.0x/gps_map_pin.png rename to np_gps_map/assets/3.0x/gps_map_pin.png diff --git a/app/assets/gps_map_pin.png b/np_gps_map/assets/gps_map_pin.png similarity index 100% rename from app/assets/gps_map_pin.png rename to np_gps_map/assets/gps_map_pin.png diff --git a/np_gps_map/lib/np_gps_map.dart b/np_gps_map/lib/np_gps_map.dart new file mode 100644 index 00000000..64b96ae2 --- /dev/null +++ b/np_gps_map/lib/np_gps_map.dart @@ -0,0 +1,3 @@ +library np_gps_map; + +export 'src/gps_map.dart'; diff --git a/np_gps_map/lib/src/gps_map.dart b/np_gps_map/lib/src/gps_map.dart new file mode 100644 index 00000000..d42a5070 --- /dev/null +++ b/np_gps_map/lib/src/gps_map.dart @@ -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 center; + final double zoom; + final void Function()? onTap; + + static bool _isNewGMapsRenderer = false; +} diff --git a/np_gps_map/lib/src/k.dart b/np_gps_map/lib/src/k.dart new file mode 100644 index 00000000..f6f287f0 --- /dev/null +++ b/np_gps_map/lib/src/k.dart @@ -0,0 +1 @@ +const libId = "com.nkming.nc_photos.np_gps_map"; diff --git a/np_gps_map/lib/src/native.dart b/np_gps_map/lib/src/native.dart new file mode 100644 index 00000000..e598f773 --- /dev/null +++ b/np_gps_map/lib/src/native.dart @@ -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 isNewGMapsRenderer() => + _methodChannel.invokeMethod("isNewGMapsRenderer").notNull(); + + static const _methodChannel = MethodChannel("${k.libId}/gps_map_method"); +} diff --git a/app/lib/mobile/google_gps_map.dart b/np_gps_map/lib/src/native/google_gps_map.dart similarity index 94% rename from app/lib/mobile/google_gps_map.dart rename to np_gps_map/lib/src/native/google_gps_map.dart index 9809111b..9f231760 100644 --- a/app/lib/mobile/google_gps_map.dart +++ b/np_gps_map/lib/src/native/google_gps_map.dart @@ -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, diff --git a/np_gps_map/lib/src/osm_gps_map.dart b/np_gps_map/lib/src/osm_gps_map.dart new file mode 100644 index 00000000..4eaf4d91 --- /dev/null +++ b/np_gps_map/lib/src/osm_gps_map.dart @@ -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 center; + final double zoom; + final void Function()? onTap; +} diff --git a/app/lib/mobile/ui_hack.dart b/np_gps_map/lib/src/ui_hack.dart similarity index 100% rename from app/lib/mobile/ui_hack.dart rename to np_gps_map/lib/src/ui_hack.dart diff --git a/app/lib/web/google_gps_map.dart b/np_gps_map/lib/src/web/google_gps_map.dart similarity index 75% rename from app/lib/web/google_gps_map.dart rename to np_gps_map/lib/src/web/google_gps_map.dart index 5507ed1a..a08e671b 100644 --- a/app/lib/web/google_gps_map.dart +++ b/np_gps_map/lib/src/web/google_gps_map.dart @@ -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 createState() => _GoogleGpsMapState(); final Tuple2 center; final double zoom; @@ -24,18 +24,18 @@ class GoogleGpsMap extends StatefulWidget { class _GoogleGpsMapState extends State { @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, ); diff --git a/np_gps_map/pubspec.yaml b/np_gps_map/pubspec.yaml new file mode 100644 index 00000000..17761afb --- /dev/null +++ b/np_gps_map/pubspec.yaml @@ -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/