diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f4d6920..283c51e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 1.1.1 + +### Improve +- [All Platform] Improve: NOverlayImage.fromWidget delete Image Widget support with assertion. +- [Example] Improve: All Examples Improve + +### Fix +- [All Platform] Fix: InfoWindow.onMarker not attached successfully (issue: [#154](https://github.com/note11g/flutter_naver_map/issues/154), PR: [#156](https://github.com/note11g/flutter_naver_map/pull/156)) +- [Android] Fix: black screen caused by flutter 3.16.0~ & android 6.0~9.0 (SDK 23~28) (issue: [#135](https://github.com/note11g/flutter_naver_map/issues/135), PR: [#153](https://github.com/note11g/flutter_naver_map/pull/153)) +- [Android] Fix: MapWidget ignore navigator stack issue android 6.0~13.0 (SDK 23~33) (issue: [#56](https://github.com/note11g/flutter_naver_map/issues/56), Temp Fix PR: [#151](https://github.com/note11g/flutter_naver_map/pull/151)) + +## 1.1.0+1 +- Update Readme & Apply Dart formatting + ## 1.1.0 ### Improve - [All Platform] Add method `controller.forceRefresh` & Update Naver Map SDK version to 3.17.0 (issue: [#116](https://github.com/note11g/flutter_naver_map/issues/116), PR: [#139](https://github.com/note11g/flutter_naver_map/pull/139)) diff --git a/README.md b/README.md index 877fa42c..3fdffb28 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,29 @@ [![pub package](https://img.shields.io/pub/v/flutter_naver_map.svg?color=4285F4)](https://pub.dev/packages/flutter_naver_map) [![github](https://img.shields.io/github/stars/note11g/flutter_naver_map)](https://github.com/note11g/flutter_naver_map) - + - - - -## Known issues + + +## Version Up Guide +`1.1.1` 이상으로 업그레이드 할 경우, 해당 코드를 다음과 같이 지워주세요.
(편의상 주석처리로 표시해두었지만, 그냥 지워주세요) + +[관련 이슈](https://github.com/note11g/flutter_naver_map/issues/56)가 해결되어, 더 이상 필요하지 않습니다. -### Android Platform -- Impeller Engine 지원 안함 (Android 기본 값은 Skia) [#133](https://github.com/note11g/flutter_naver_map/issues/133) -- **[WIP]** flutter 3.16 버전부터 Android 9.0(SDK 28) 이하 작동 오류 [#135](https://github.com/note11g/flutter_naver_map/issues/135) ([임시 해결 PR #148](https://github.com/note11g/flutter_naver_map/pull/148)) -- **[WIP]** native_splash_screen 패키지 동시 사용 지원 안함 [#56](https://github.com/note11g/flutter_naver_map/issues/56) +```kotlin +// android/app/main/.../MainActivity.kt +class MainActivity : FlutterActivity() // { +// override fun onCreate(savedInstanceState: Bundle?) { +// intent.putExtra("background_mode", "transparent") +// super.onCreate(savedInstanceState) +// } +// } +``` + +## Known issues +- Android Impeller Engine 지원 안함 (Android 기본 값은 Skia) [#133](https://github.com/note11g/flutter_naver_map/issues/133) 이슈 제보는 언제나 환영입니다:) diff --git a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/FlutterNaverMapPlugin.kt b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/FlutterNaverMapPlugin.kt index 1067a0d8..45f8aac8 100644 --- a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/FlutterNaverMapPlugin.kt +++ b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/FlutterNaverMapPlugin.kt @@ -1,5 +1,6 @@ package dev.note11.flutter_naver_map.flutter_naver_map +import android.app.Activity import android.content.Context import dev.note11.flutter_naver_map.flutter_naver_map.sdk.SdkInitializer import dev.note11.flutter_naver_map.flutter_naver_map.view.NaverMapViewFactory @@ -28,14 +29,19 @@ internal class FlutterNaverMapPlugin : FlutterPlugin, ActivityAware { override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) = Unit override fun onAttachedToActivity(binding: ActivityPluginBinding) { + val activity = binding.activity val naverMapViewFactory = - NaverMapViewFactory(binding.activity, pluginBinding.binaryMessenger) + NaverMapViewFactory(activity, pluginBinding.binaryMessenger) + correctDisplayOnFlutterNavigatorStackWithActivityBackgroundMode(activity) pluginBinding.platformViewRegistry.registerViewFactory( - MAP_VIEW_TYPE_ID, - naverMapViewFactory + MAP_VIEW_TYPE_ID, naverMapViewFactory ) } + private fun correctDisplayOnFlutterNavigatorStackWithActivityBackgroundMode(activity: Activity) { + activity.intent.putExtra("background_mode", "transparent") + } + override fun onDetachedFromActivityForConfigChanges() = Unit override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) = Unit diff --git a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/controller/overlay/OverlayController.kt b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/controller/overlay/OverlayController.kt index 1bb214df..98616fb7 100644 --- a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/controller/overlay/OverlayController.kt +++ b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/controller/overlay/OverlayController.kt @@ -254,6 +254,7 @@ internal class OverlayController( success: (Any?) -> Unit, ) { val nInfoWindow = NInfoWindow.fromMessageable(rawInfoWindow, context = context) + .apply { setCommonProperties(rawInfoWindow.asMap()) } val infoWindow = saveOverlayWithAddable(creator = nInfoWindow) val align = rawAlign.asAlign() diff --git a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/converter/AddableOverlay.kt b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/converter/AddableOverlay.kt index 99c201f4..9ce0df2e 100644 --- a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/converter/AddableOverlay.kt +++ b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/converter/AddableOverlay.kt @@ -26,7 +26,7 @@ internal abstract class AddableOverlay { private lateinit var commonProperties: Map - private fun setCommonProperties(rawArgs: Map) { + fun setCommonProperties(rawArgs: Map) { commonProperties = rawArgs.filter { OverlayHandler.allPropertyNames.contains(it.key) } } diff --git a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/sdk/SdkInitializer.kt b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/sdk/SdkInitializer.kt index 351288d1..2b72452e 100644 --- a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/sdk/SdkInitializer.kt +++ b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/sdk/SdkInitializer.kt @@ -36,7 +36,7 @@ internal class SdkInitializer( try { if (clientId != null) initializeMapSdk(context, clientId, isGov) if (setAuthFailedListener) setOnAuthFailedListener() - val sendPayload = mapOf("androidSdkVersion" to androidSdkVersion) + val sendPayload = mapOf("androidSdkVersion" to Build.VERSION.SDK_INT) onSuccess(sendPayload) } catch (e: NaverMapSdk.AuthFailedException) { onFailure(e) @@ -63,8 +63,4 @@ internal class SdkInitializer( ) ) } - - companion object { - private val androidSdkVersion: Int get() = Build.VERSION.SDK_INT - } } \ No newline at end of file diff --git a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapView.kt b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapView.kt index c365c261..9a077bfd 100644 --- a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapView.kt +++ b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapView.kt @@ -3,7 +3,9 @@ package dev.note11.flutter_naver_map.flutter_naver_map.view import android.app.Activity import android.app.Application import android.content.ComponentCallbacks +import android.content.Context import android.content.res.Configuration +import android.os.Build import android.os.Bundle import android.view.View import com.naver.maps.map.MapView @@ -21,6 +23,7 @@ import io.flutter.plugin.platform.PlatformView internal class NaverMapView( private val activity: Activity, + private val flutterProvidedContext: Context, private val naverMapViewOptions: NaverMapViewOptions, private val channel: MethodChannel, private val overlayController: OverlayHandler, @@ -28,13 +31,16 @@ internal class NaverMapView( private lateinit var naverMap: NaverMap private lateinit var naverMapControlSender: NaverMapControlSender - private val mapView = MapView(activity, naverMapViewOptions.naverMapOptions).apply { - setTempMethodCallHandler() - getMapAsync { naverMap -> - this@NaverMapView.naverMap = naverMap - onMapReady() + private val mapView = + MapView(flutterProvidedContext, naverMapViewOptions.naverMapOptions.apply { + useTextureView(Build.VERSION.SDK_INT <= 29) + }).apply { + setTempMethodCallHandler() + getMapAsync { naverMap -> + this@NaverMapView.naverMap = naverMap + onMapReady() + } } - } private var isListenerRegistered = false private var rawNaverMapOptionTempCache: Any? = null @@ -60,7 +66,7 @@ internal class NaverMapView( private fun initializeMapController() { naverMapControlSender = NaverMapController( - naverMap, channel, activity.applicationContext, overlayController + naverMap, channel, flutterProvidedContext, overlayController ).apply { rawNaverMapOptionTempCache?.let { updateOptions(it.asMap()) {} } } diff --git a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapViewFactory.kt b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapViewFactory.kt index 030cf8c0..c26b255e 100644 --- a/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapViewFactory.kt +++ b/android/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map/view/NaverMapViewFactory.kt @@ -29,6 +29,7 @@ internal class NaverMapViewFactory( return NaverMapView( activity = activity, + flutterProvidedContext = context, naverMapViewOptions = options, channel = channel, overlayController = overlayController, diff --git a/example/android/app/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map_example/MainActivity.kt b/example/android/app/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map_example/MainActivity.kt index c1054ab4..87bbc437 100644 --- a/example/android/app/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map_example/MainActivity.kt +++ b/example/android/app/src/main/kotlin/dev/note11/flutter_naver_map/flutter_naver_map_example/MainActivity.kt @@ -3,9 +3,4 @@ package dev.note11.flutter_naver_map.flutter_naver_map_example import android.os.Bundle import io.flutter.embedding.android.FlutterActivity -class MainActivity : FlutterActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - intent.putExtra("background_mode", "transparent") - super.onCreate(savedInstanceState) - } -} +class MainActivity : FlutterActivity() {} diff --git a/example/integration_test/overlay_test.dart b/example/integration_test/overlay_test.dart index 97012897..81dd65f4 100644 --- a/example/integration_test/overlay_test.dart +++ b/example/integration_test/overlay_test.dart @@ -135,5 +135,42 @@ void main() { marker.setAlpha(0.8); expect(marker.alpha, 0.8); }); + + /// #154 issue test (https://github.com/note11g/flutter_naver_map/issues/154) + testNaverMap("NInfoWindow.onMarker test", (controller, tester) async { + final cameraTarget = + await controller.getCameraPosition().then((cp) => cp.target); + final marker = NMarker(id: "1", position: cameraTarget); + await controller.addOverlay(marker); + final infoWindow = + NInfoWindow.onMarker(id: "2", text: "infowindow", offsetX: 0.1) + ..setMinZoom(13); + await marker.openInfoWindow(infoWindow); + expect(infoWindow.offsetX, 0.1); + expect(infoWindow.minZoom, 13); + + await Future.delayed(const Duration(milliseconds: 200)); + final screenTarget = + await controller.latLngToScreenLocation(cameraTarget); + + Future> pickNOverlayInfo() { + return controller + .pickAll(screenTarget, radius: 800) + .then((pickable) => pickable.whereType().toList()); + } + + final pickedInfoListByDefaultZoom = await pickNOverlayInfo(); + print(pickedInfoListByDefaultZoom); + expect(pickedInfoListByDefaultZoom.contains(infoWindow.info), true); + + await controller.updateCamera(NCameraUpdate.scrollAndZoomTo(zoom: 12) + ..setAnimation(duration: Duration.zero)); + + await Future.delayed(const Duration(milliseconds: 200)); + + final pickedInfoListWhenZoomOut = await pickNOverlayInfo(); + print(pickedInfoListWhenZoomOut); + expect(pickedInfoListWhenZoomOut.contains(infoWindow.info), false); + }); }); } diff --git a/example/lib/design/custom_widget.dart b/example/lib/design/custom_widget.dart index 33a04796..8d15bc28 100644 --- a/example/lib/design/custom_widget.dart +++ b/example/lib/design/custom_widget.dart @@ -452,8 +452,7 @@ class BottomPadding extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: - EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom)); + padding: EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom)); } } @@ -571,8 +570,9 @@ class _BalloonPainter extends CustomPainter { } class HalfActionButton extends StatelessWidget { - final Function() action; + final Function()? action; final IconData icon; + final Color? color; final String title; final String description; @@ -580,6 +580,7 @@ class HalfActionButton extends StatelessWidget { super.key, required this.action, required this.icon, + this.color, required this.title, required this.description, }); @@ -587,7 +588,7 @@ class HalfActionButton extends StatelessWidget { @override Widget build(BuildContext context) { return Material( - color: getColorTheme(context).outlineVariant, + color: color ?? getColorTheme(context).outline, borderRadius: BorderRadius.circular(12), clipBehavior: Clip.antiAlias, child: InkWell( @@ -596,28 +597,24 @@ class HalfActionButton extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12), child: Row(children: [ - Icon(icon, - color: getColorTheme(context).primary, size: 22), + Icon(icon, color: getColorTheme(context).primary, size: 22), const SizedBox(width: 10), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, - style: getTextTheme(context) - .labelMedium - ?.copyWith(color: Colors.black), - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1), - const SizedBox(height: 2), - Text(description, - style: getTextTheme(context).bodySmall, - overflow: TextOverflow.fade, - softWrap: false, - maxLines: 1), - ]), - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, + style: getTextTheme(context).labelMedium, + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1), + const SizedBox(height: 2), + Text(description, + style: getTextTheme(context).bodySmall, + overflow: TextOverflow.fade, + softWrap: false, + maxLines: 1), + ])), ])))); } } @@ -654,3 +651,51 @@ class HalfActionButtonGrid extends StatelessWidget { return Column(children: rowCellAndGapWidgets); } } + +class SmallButton extends StatelessWidget { + final String text; + final VoidCallback onTap; + final Color? color; + final Color? textColor; + final IconData? icon; + final double radius; + + const SmallButton( + this.text, { + super.key, + required this.onTap, + this.color, + this.textColor, + this.icon, + this.radius = 6, + }); + + BorderRadius get borderRadius => BorderRadius.circular(radius); + + @override + Widget build(BuildContext context) { + return Material( + color: color ?? getColorTheme(context).secondary, + borderRadius: borderRadius, + child: InkWell( + onTap: onTap, + borderRadius: borderRadius, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (icon != null) + Padding( + padding: const EdgeInsets.only(right: 4), + child: Icon(icon, + color: textColor ?? Colors.white, size: 16), + ), + Text(text, + style: getTextTheme(context) + .labelSmall + ?.copyWith(color: textColor)), + ])))); + } +} diff --git a/example/lib/design/map_function_item.dart b/example/lib/design/map_function_item.dart index dec7fc21..07aa5cb3 100644 --- a/example/lib/design/map_function_item.dart +++ b/example/lib/design/map_function_item.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bottom_drawer/flutter_bottom_drawer.dart'; +import 'package:flutter_naver_map_example/design/custom_widget.dart'; import 'theme.dart'; @@ -8,7 +9,8 @@ typedef MapFunctionItemOnBack = void Function(); class MapFunctionItem { final String title; - final String? description; + final String description; + final IconData icon; final MapFunctionItemOnTap? onTap; final Widget Function(bool canScroll)? page; final MapFunctionItemOnBack? onBack; @@ -18,7 +20,8 @@ class MapFunctionItem { MapFunctionItem({ required this.title, - this.description, + required this.description, + required this.icon, this.page, this.onTap, this.onBack, @@ -29,35 +32,13 @@ class MapFunctionItem { bool get needItemOnTap => onTap == null; Widget getItemWidget(BuildContext context) { - return InkWell( - onTap: onTap != null ? () => onTap!.call(this) : null, - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 24, vertical: description != null ? 12 : 20), - child: - Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, style: Theme.of(context).textTheme.titleSmall), - if (description != null) - Padding( - padding: const EdgeInsets.only(top: 4), - child: SizedBox( - width: double.infinity, - child: Text(description!, - style: getTextTheme(context) - .bodyMedium - ?.copyWith( - color: - getColorTheme(context).secondary), - overflow: TextOverflow.ellipsis, - maxLines: 1))) - ])), - const Icon(Icons.arrow_forward_ios_rounded, size: 20), - ]))); + return HalfActionButton( + action: onTap != null ? () => onTap!.call(this) : null, + title: title, + color: getColorTheme(context).onSurface, + description: description, + icon: icon, + ); } Widget getPageWidget(BuildContext context, {bool canScroll = true}) { @@ -113,6 +94,7 @@ class MapFunctionItem { MapFunctionItemOnBack? onBack, bool? isScrollPage, DrawerMoveController? drawerController, + IconData? icon, }) { return MapFunctionItem( title: title ?? this.title, @@ -122,6 +104,7 @@ class MapFunctionItem { onBack: onBack ?? this.onBack, isScrollPage: isScrollPage ?? this.isScrollPage, drawerController: drawerController ?? this.drawerController, + icon: icon ?? this.icon, ); } } diff --git a/example/lib/design/theme.dart b/example/lib/design/theme.dart index db63e2c9..2b6c1414 100644 --- a/example/lib/design/theme.dart +++ b/example/lib/design/theme.dart @@ -12,6 +12,7 @@ class ExampleAppTheme { outline: Colors.grey.shade200, outlineVariant: Colors.grey.shade200, primaryContainer: const Color(0xFFD2FFB4), + onSurface: Colors.grey.shade100, ), textTheme: const TextTheme( titleSmall: TextStyle( @@ -29,7 +30,7 @@ class ExampleAppTheme { labelMedium: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, - color: Colors.white, + color: Colors.black, letterSpacing: 0), labelLarge: TextStyle( fontSize: 16, @@ -49,6 +50,7 @@ class ExampleAppTheme { outline: Colors.grey.shade700, outlineVariant: Colors.white54, primaryContainer: const Color(0xFF7FA864), + onSurface: Colors.grey.shade800, ), textTheme: const TextTheme( titleSmall: TextStyle( diff --git a/example/lib/main.dart b/example/lib/main.dart index 2d2fe0e8..71db1c25 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,12 +1,17 @@ import 'dart:async'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_naver_map/flutter_naver_map.dart'; +import 'package:flutter_naver_map_example/pages/examples/camera_example.dart'; import 'package:flutter_naver_map_example/pages/examples/controller_example.dart'; import 'package:flutter_naver_map_example/pages/examples/overlay_example.dart'; +import 'package:flutter_naver_map_example/pages/examples/pick_example.dart'; +import 'package:flutter_naver_map_example/util/overlay_portal_util.dart'; +import 'package:transparent_pointer/transparent_pointer.dart'; -import 'pages/bottom_drawer.dart'; -import 'pages/new_window_page.dart'; +import 'pages/utils/bottom_drawer.dart'; +import 'pages/utils/new_window_page.dart'; import 'design/map_function_item.dart'; import 'design/theme.dart'; @@ -49,16 +54,26 @@ class _FNMapPageState extends State { late EdgeInsets safeArea; double drawerHeight = 0; + final nOverlayInfoOverlayPortalController = NInfoOverlayPortalController(); + @override Widget build(BuildContext context) { safeArea = MediaQuery.of(context).padding; - return WillPopScope( - onWillPop: () async => drawerTool.processWillPop(), - child: Stack(children: [ - Positioned.fill(child: mapWidget()), - drawerTool.bottomDrawer, - ]), - ); + return Scaffold( + body: PopScope( + onPopInvoked: (didPop) => drawerTool.processWillPop(), + child: Stack(children: [ + GestureDetector( + onTapDown: (details) => _onLastTouchStreamController.sink + .add(details.globalPosition)), + Positioned.fill(child: TransparentPointer(child: mapWidget())), + drawerTool.bottomDrawer, + OverlayPortal( + controller: nOverlayInfoOverlayPortalController, + overlayChildBuilder: (context) => + nOverlayInfoOverlayPortalController.builder( + context, mapController)), + ]))); } /* @@ -87,7 +102,7 @@ class _FNMapPageState extends State { mapController = controller; } - void onMapTapped(NPoint point, NLatLng latLng) { + void onMapTapped(NPoint point, NLatLng latLng) async { // ... } @@ -108,7 +123,8 @@ class _FNMapPageState extends State { // ... } - final _onCameraChangeStreamController = StreamController.broadcast(); + final _onCameraChangeStreamController = StreamController.broadcast(); + final _onLastTouchStreamController = StreamController.broadcast(); /* --- Bottom Drawer Widget --- @@ -122,8 +138,9 @@ class _FNMapPageState extends State { late final List pages = [ MapFunctionItem( - title: "NaverMapViewOptions 변경", - description: "지도의 옵션을 변경할 수 있어요", + title: "지도 위젯 옵션 변경하기", + description: "위젯에 보여지는 걸 바꿔봐요", + icon: Icons.map_rounded, page: (canScroll) => NaverMapViewOptionsExample( canScroll: canScroll, options: options, @@ -132,61 +149,54 @@ class _FNMapPageState extends State { })), MapFunctionItem( title: "오버레이 추가 / 제거", - description: "마커, 경로 등의 각종 오버레이들을 추가하고 제거할 수 있어요", + description: "마커/경로/도형 등을 띄워봐요", + icon: Icons.add_location_alt_rounded, isScrollPage: false, page: (canScroll) => NOverlayExample( - canScroll: canScroll, mapController: mapController)), + nOverlayInfoOverlayPortalController: + nOverlayInfoOverlayPortalController, + canScroll: canScroll, + mapController: mapController)), MapFunctionItem( title: "카메라 이동", - description: "지도에 보이는 영역을 카메라를 이동하여 바꿀 수 있어요", - page: (canScroll) => _cameraMoveTestPage(mapController)), + isScrollPage: false, + icon: Icons.zoom_in_rounded, + description: "지도를 요리조리 움직여봐요", + page: (canScroll) => CameraUpdateExample( + onCameraChangeStream: _onCameraChangeStreamController.stream, + canScroll: canScroll, + mapController: mapController)), + MapFunctionItem( + title: "주변 Pickable 보기", + description: "주변 심볼, 오버레이를 찾아봐요", + icon: Icons.domain_rounded, + page: (canScroll) { + final screenSize = MediaQuery.sizeOf(context); + return NaverMapPickExample( + canScroll: canScroll, + mapController: mapController, + mapEndPoint: + Point(screenSize.width, screenSize.height - drawerHeight), + onCameraChangeStream: _onCameraChangeStreamController.stream, + ); + }), MapFunctionItem( title: "기타 컨트롤러 기능", - description: "컨트롤러로 지도의 상태를 가져오거나 변경할 수 있습니다.", + description: "컨트롤러 기능을 살펴봐요", isScrollPage: false, + icon: Icons.sports_esports_rounded, page: (canScroll) => NaverMapControllerExample( canScroll: canScroll, mapController: mapController, onCameraChangeStream: _onCameraChangeStreamController.stream, + onLastTouchStream: _onLastTouchStreamController.stream, )), - MapFunctionItem( - title: "주변 심볼 및 오버레이 가져오기", - description: "특정 영역 주변의 심볼 및 오버레이를 가져올 수 있어요", - page: (canScroll) => _pickTestPage()), MapFunctionItem( title: "새 페이지에서 지도 보기", - description: "새 페이지에서 지도를 봅니다. (메모리 누수 확인용)", + icon: Icons.note_add_rounded, + description: "테스트용이에요", onTap: (_) => Navigator.of(context).push( MaterialPageRoute(builder: (context) => const NewWindowTestPage())), ), ]; - - Widget _cameraMoveTestPage(NaverMapController mapController) { - return Padding( - padding: const EdgeInsets.all(24), - child: Column(children: [ - // todo - const Text("_cameraMoveTestPage"), - const Text("카메라 이동"), - ElevatedButton( - onPressed: () { - mapController.updateCamera(NCameraUpdate.fromCameraPosition( - const NCameraPosition( - target: NLatLng(37.56362422812855, 126.96269803941277), - zoom: 17.00922642853924, - bearing: 119.62995870263971))); - }, - child: const Text('카메라 회전')), - ])); - } - - Widget _pickTestPage() { - return const Padding( - padding: EdgeInsets.all(24), - child: Column(children: [ - // todo - Text("_pickTestPage"), - Text("주변 심볼 및 오버레이 가져오기"), - ])); - } } diff --git a/example/lib/pages/bottom_drawer.dart b/example/lib/pages/bottom_drawer.dart deleted file mode 100644 index 2114a21e..00000000 --- a/example/lib/pages/bottom_drawer.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bottom_drawer/flutter_bottom_drawer.dart'; - -import '../design/map_function_item.dart'; -import '../design/theme.dart'; - -class ExampleAppBottomDrawer { - final BuildContext context; - final Function(double height) onDrawerHeightChanged; - final Function() rebuild; - final List pages; - final void Function()? onPageDispose; - - ExampleAppBottomDrawer({ - required this.context, - required this.onDrawerHeightChanged, - required this.rebuild, - required this.pages, - this.onPageDispose, - }); - - ColorScheme get colorTheme => getColorTheme(context); - - TextTheme get textTheme => getTextTheme(context); - - late DrawerMoveController drawerController; - late DrawerState drawerState; - late Function(Function()) drawerSetState; - final scrollController = ScrollController(); - - MapFunctionItem? nowItem; - - bool get hasPage => nowItem != null; - - void go(MapFunctionItem item) { - nowItem = item; - rebuildDrawerAndPage(); - } - - void back() { - nowItem = null; - onPageDispose?.call(); - rebuildDrawerAndPage(); - } - - void rebuildDrawerAndPage() { - drawerSetState(() {}); // drawer rebuild - rebuild(); // page rebuild (height changed) - } - - BottomDrawer get bottomDrawer => BottomDrawer( - height: nowItem?.isScrollPage == false ? null : 200, - expandedHeight: 480, - handleSectionHeight: 20, - handleColor: colorTheme.secondary, - backgroundColor: colorTheme.background, - onReady: (controller) => drawerController = controller, - onStateChanged: (state) => drawerState = state, - onHeightChanged: onDrawerHeightChanged, - builder: (state, setState, context) { - drawerSetState = setState; - return hasPage - ? selectedPage(state == DrawerState.opened) - : innerListView(state == DrawerState.opened); - }); - - Widget innerListView(bool canScroll) => Column(children: [ - innerListViewHeader(), - Expanded( - child: ScrollConfiguration( - behavior: const ScrollBehavior().copyWith(overscroll: false), - child: Scrollbar( - controller: scrollController, - child: ListView.builder( - controller: scrollController, - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom), - physics: canScroll - ? const ClampingScrollPhysics() - : const NeverScrollableScrollPhysics(), - itemCount: pages.length, - itemBuilder: (context, index) { - MapFunctionItem page = pages[index]; - if (page.needItemOnTap) { - page = page.copyWith( - onTap: go, - onBack: back, - drawerController: drawerController); - } - return page.getItemWidget(context); - }, - )))), - ]); - - Widget innerListViewHeader() => Container( - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: colorTheme.onBackground.withOpacity(0.28), width: 0.2)), - ), - padding: const EdgeInsets.fromLTRB(24, 12, 24, 16), - child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("지도 기능 둘러보기", style: textTheme.titleLarge), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4), - decoration: BoxDecoration( - color: kReleaseMode ? colorTheme.primary : colorTheme.secondary, - borderRadius: BorderRadius.circular(4), - ), - child: Text(kReleaseMode ? "Release Mode" : "Debug Mode", - style: textTheme.labelSmall)), - ])); - - Widget selectedPage(bool canScroll) { - assert(nowItem != null); - return nowItem!.getPageWidget(context, canScroll: canScroll); - } - - bool processWillPop() { - if (hasPage) { - back(); - return false; - } else if (drawerState != DrawerState.closed) { - drawerController.close(); - return false; - } - return true; - } -} diff --git a/example/lib/pages/examples/camera_example.dart b/example/lib/pages/examples/camera_example.dart new file mode 100644 index 00000000..e9108d15 --- /dev/null +++ b/example/lib/pages/examples/camera_example.dart @@ -0,0 +1,275 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_naver_map/flutter_naver_map.dart'; +import 'package:flutter_naver_map_example/design/theme.dart'; +import 'package:flutter_naver_map_example/pages/utils/example_base.dart'; +import 'package:flutter_naver_map_example/util/string_util.dart'; + +import '../../design/custom_widget.dart'; + +class CameraUpdateExample extends ExampleBasePage { + final Stream onCameraChangeStream; + + const CameraUpdateExample({ + Key? key, + required super.mapController, + required super.canScroll, + required this.onCameraChangeStream, + }) : super(key: key); + + @override + State createState() => _NOverlayExampleState(); +} + +class _NOverlayExampleState extends State { + /// 현재 카메라 상태 + NCameraPosition? _nowCameraPosition; + int _animationMill = 300; + static const dpForMove = 40.0; + + void onCameraChange() async { + _nowCameraPosition = await _mapController.getCameraPosition(); + if (mounted) setState(() {}); + } + + void updateCamera(NCameraUpdate cameraUpdate) { + _mapController.updateCamera(cameraUpdate + ..setAnimation(duration: Duration(milliseconds: _animationMill))); + } + + void moveCameraCoordWithDp(double dp, Axis axis) async { + final meterPerDp = await _mapController.getMeterPerDp(); + final offsetMeter = meterPerDp * dp; + updateCamera(NCameraUpdate.withParams( + target: _nowCameraPosition!.target.offsetByMeter( + eastMeter: axis == Axis.horizontal ? offsetMeter : 0, + northMeter: axis == Axis.vertical ? offsetMeter : 0, + ))); + } + + Widget coordControlWidget() { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 20) + .copyWith(right: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: InnerSimpleTitle( + title: + "좌표 (${_nowCameraPosition?.target.toShortString()})", + description: + "NCameraUpdate.scrollAndZoomTo, .target,\nNCameraPosition.target")), + _arrowButtonController( + up: () => moveCameraCoordWithDp(dpForMove, Axis.vertical), + down: () => moveCameraCoordWithDp(-dpForMove, Axis.vertical), + left: () => moveCameraCoordWithDp(-dpForMove, Axis.horizontal), + right: () => moveCameraCoordWithDp(dpForMove, Axis.horizontal), + ), + ])); + } + + Widget _arrowButtonController({ + required VoidCallback left, + required VoidCallback right, + required VoidCallback up, + required VoidCallback down, + }) { + Widget buttonSeparator([bool horizontal = false]) => Container( + width: horizontal ? null : 1, + height: horizontal ? 1 : null, + color: getColorTheme(context).onSurface); + + Widget arrowButton(IconData icon, void Function() onTap, + {double width = 32, double height = 48}) => + Material( + color: getColorTheme(context).outline, + child: InkWell( + onTap: onTap, + child: Container( + width: width, + height: height, + alignment: Alignment.center, + child: Icon(icon, size: 20)))); + + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: IntrinsicHeight( + child: Row(children: [ + arrowButton(Icons.arrow_left_rounded, left), + buttonSeparator(), + SizedBox( + width: 40, + height: 48, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: arrowButton(Icons.arrow_drop_up_rounded, up)), + buttonSeparator(true), + Expanded( + child: + arrowButton(Icons.arrow_drop_down_rounded, down)), + ])), + buttonSeparator(), + arrowButton(Icons.arrow_right_rounded, right), + ]))); + } + + Widget zoomControlWidget() { + return plusMinusItemWidget( + title: "줌 레벨", + method: + "NCameraUpdate.scrollAndZoomTo, .zoom,\n.zoomBy, NCameraPosition.zoom", + value: _nowCameraPosition?.zoom.toStringAsFixed(1), + resetFunc: () => updateCamera(NCameraUpdate.withParams(zoom: 14)), + minusFunc: () => updateCamera(NCameraUpdate.zoomBy(-0.5)), + plusFunc: () => updateCamera(NCameraUpdate.zoomBy(0.5)), + ); + } + + Widget tiltControlWidget() { + return plusMinusItemWidget( + title: "틸트 레벨", + method: "NCameraUpdate.tilt, .tiltBy,\nNCameraPosition.tilt", + value: _nowCameraPosition?.tilt.toStringAsFixed(1), + resetFunc: () => updateCamera(NCameraUpdate.withParams(tilt: 0)), + minusFunc: () => updateCamera(NCameraUpdate.withParams(tiltBy: -9)), + plusFunc: () => updateCamera(NCameraUpdate.withParams(tiltBy: 9)), + ); + } + + Widget bearingControlWidget() { + return plusMinusItemWidget( + title: "방향각(도)", + method: "NCameraUpdate.bearing, .bearingBy,\nNCameraPosition.bearing", + value: _nowCameraPosition?.bearing.toStringAsFixed(1), + resetFunc: () => updateCamera(NCameraUpdate.withParams(bearing: 0)), + minusFunc: () => updateCamera(NCameraUpdate.withParams(bearingBy: -20)), + plusFunc: () => updateCamera(NCameraUpdate.withParams(bearingBy: 20)), + ); + } + + Widget animationSpeedControlWidget() { + return plusMinusItemWidget( + title: "이동 애니메이션 시간", + method: "NCameraUpdate.setAnimation(\n\tNCameraAnimation?, Duration?)", + value: "${_animationMill}ms", + resetFunc: () => setState(() => _animationMill = 800), + minusFunc: () => setState(() => _animationMill -= 100), + plusFunc: () => setState(() => _animationMill += 100), + ); + } + + Widget plusMinusItemWidget({ + required String title, + required String method, + required String? value, + required void Function() resetFunc, + required void Function() plusFunc, + required void Function() minusFunc, + }) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 20) + .copyWith(right: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: InnerSimpleTitle( + title: title, + description: method, + direction: Axis.vertical)), + Row(children: [ + IconButton( + onPressed: minusFunc, + icon: const Icon(Icons.remove_circle)), + Material( + borderRadius: BorderRadius.circular(8), + color: getColorTheme(context).outline, + child: InkWell( + onTap: resetFunc, + borderRadius: BorderRadius.circular(8), + child: Stack(children: [ + Container( + constraints: const BoxConstraints(minWidth: 60), + padding: const EdgeInsets.symmetric(vertical: 10), + alignment: Alignment.center, + child: Text(value.toString(), + style: getTextTheme(context).titleSmall), + ), + Positioned( + top: 2, + right: 2, + child: Icon(Icons.refresh_rounded, + size: 12, + color: getColorTheme(context).secondary)), + ]))), + IconButton( + onPressed: plusFunc, icon: const Icon(Icons.add_circle)), + ]), + ])); + } + + @override + Widget build(BuildContext context) { + return Column(children: [ + coordControlWidget(), + zoomControlWidget(), + if (widget.canScroll) + Column(children: [ + tiltControlWidget(), + bearingControlWidget(), + animationSpeedControlWidget(), + ]), + const BottomPadding(), + ]); + } + + bool _onKeyUp(KeyEvent event) { + if (event is KeyDownEvent) return true; + + final _ = switch (event.logicalKey) { + LogicalKeyboardKey.arrowUp => + moveCameraCoordWithDp(dpForMove, Axis.vertical), + LogicalKeyboardKey.arrowDown => + moveCameraCoordWithDp(-dpForMove, Axis.vertical), + LogicalKeyboardKey.arrowLeft => + moveCameraCoordWithDp(-dpForMove, Axis.horizontal), + LogicalKeyboardKey.arrowRight => + moveCameraCoordWithDp(dpForMove, Axis.horizontal), + LogicalKeyboardKey.minus => updateCamera(NCameraUpdate.zoomBy(-0.5)), + LogicalKeyboardKey.add || + LogicalKeyboardKey.equal => + updateCamera(NCameraUpdate.zoomBy(0.5)), + _ => null, + }; + + return false; + } + + NaverMapController get _mapController => widget.mapController; + StreamSubscription? onCameraChangeStreamSubscription; + StreamSubscription? onKeyUpStreamSubscription; + + @override + void initState() { + super.initState(); + HardwareKeyboard.instance.addHandler(_onKeyUp); + onCameraChange(); + onCameraChangeStreamSubscription = + widget.onCameraChangeStream.listen((_) => onCameraChange()); + } + + @override + void dispose() { + HardwareKeyboard.instance.removeHandler(_onKeyUp); + onCameraChangeStreamSubscription?.cancel(); + onCameraChangeStreamSubscription = null; + super.dispose(); + } +} diff --git a/example/lib/pages/examples/controller_example.dart b/example/lib/pages/examples/controller_example.dart index 601e73ae..04f4efab 100644 --- a/example/lib/pages/examples/controller_example.dart +++ b/example/lib/pages/examples/controller_example.dart @@ -1,22 +1,26 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_naver_map/flutter_naver_map.dart'; import 'package:flutter_naver_map_example/design/custom_widget.dart'; -import 'package:flutter_naver_map_example/pages/example_base.dart'; +import 'package:flutter_naver_map_example/pages/utils/example_base.dart'; import 'package:flutter_naver_map_example/util/alert_util.dart'; +import 'package:flutter_naver_map_example/util/string_util.dart'; import '../../design/theme.dart'; class NaverMapControllerExample extends ExampleBasePage { final Stream onCameraChangeStream; + final Stream onLastTouchStream; const NaverMapControllerExample({ super.key, required super.mapController, required super.canScroll, required this.onCameraChangeStream, + required this.onLastTouchStream, }); @override @@ -31,10 +35,25 @@ class _NaverMapControllerExampleState extends State { /// 현재 m/dp (1dp가 몇 미터인지) double? _nowMeterPerDp; + /// 현재 표출되는 지도 범위 + NLatLngBounds? _regionBounds; + + /// 마지막 터치 화면 좌표 + NPoint? _lastTappedScreenPosition; + NLatLng? _lastTappedMapPosition; + void onCameraChange() async { _nowCameraPosition = await _mapController.getCameraPosition(); _nowMeterPerDp = await _mapController.getMeterPerDp(); - setState(() {}); + _regionBounds = await _mapController.getContentBounds(); + if (mounted) setState(() {}); + } + + void onLastTouch(Offset offset) { + _lastTappedScreenPosition = NPoint(offset.dx, offset.dy); + _mapController + .screenLocationToLatLng(_lastTappedScreenPosition!) + .then((latLng) => _lastTappedMapPosition = latLng); } Widget _nowCameraPositionWidget() { @@ -101,19 +120,54 @@ class _NaverMapControllerExampleState extends State { ])); } - Widget _actionButtonSections(bool isUnFold) { + Widget _contentsRegionWidget() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + const InnerSimpleTitle( + title: "지도 위젯에 보여지는 범위", + description: ".getContentBounds() | .getContentRegion()"), + const SizedBox(height: 4), + Text( + "남서: ${_regionBounds?.southWest.toShortString()}, " + "북동: ${_regionBounds?.northEast.toShortString()}", + style: getTextTheme(context) + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w700)), + ])); + } + + Widget _switchLatLngToScreenLocationWidget() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + const InnerSimpleTitle( + title: "위치 좌표 ↔️ 화면 좌표", + description: + ".screenLocationToLatLng(NPoint)\n.latLngToScreenLocation(NLatLng)"), + const SizedBox(height: 4), + Text( + _lastTappedScreenPosition != null + ? "마지막 드래그 화면 좌표: NPoint(${_lastTappedScreenPosition!.x.toStringAsFixed(5)}, ${_lastTappedScreenPosition!.y.toStringAsFixed(5)})\n" + "변환 된 지도 좌표: NLatLng(${_lastTappedMapPosition?.toShortString()})" + : "지도를 드래그해보세요 (드래그 시작점을 수집합니다)", + style: getTextTheme(context) + .bodyMedium + ?.copyWith(fontWeight: FontWeight.w700)), + ])); + } + + Widget _actionButtonSections() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), child: HalfActionButtonGrid(buttons: [ - if (isUnFold) HalfActionButton( action: _mapController.forceRefresh, icon: Icons.refresh, title: "지도 강제 새로고침", description: ".forceRefresh"), HalfActionButton( - action: () => AlertUtil.openAlert("준비중인 예제입니다.\n함수로는 사용하실 수 있습니다.", - context: context), + action: _takeSnapshot, icon: Icons.camera_alt, title: "지도 캡쳐하기", description: ".takeSnapshot"), @@ -121,20 +175,61 @@ class _NaverMapControllerExampleState extends State { ); } + void _takeSnapshot() async { + final snapshot = await _mapController.takeSnapshot(showControls: false); + if (context.mounted) { + showDialog( + context: context, + builder: (context) { + return Center( + child: Material( + color: getColorTheme(context).background, + borderRadius: BorderRadius.circular(12), + child: Container( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("지도 캡쳐하기", + style: getTextTheme(context).titleMedium), + Container( + margin: const EdgeInsets.only(top: 6), + height: MediaQuery.sizeOf(context).height * 0.64, + child: Image.file(File(snapshot.path))), + ])), + )); + }); + } + } + @override Widget build(BuildContext context) { - return Column(children: [ - _nowCameraPositionWidget(), - _meterPerDpWidget(), - _actionButtonSections(widget.canScroll), - const BottomPadding(), - ]); + return Expanded( + flex: widget.canScroll ? 1 : 0, + child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + _nowCameraPositionWidget(), + if (widget.canScroll) + Expanded( + child: ListView( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom), + children: [ + _actionButtonSections(), + _meterPerDpWidget(), + _contentsRegionWidget(), + _switchLatLngToScreenLocationWidget(), + ])), + if (!widget.canScroll) const BottomPadding(), + ]), + ); } // --- worker --- NaverMapController get _mapController => widget.mapController; - StreamSubscription? onCameraChangeStreamSubscription; + StreamSubscription? onCameraChangeStreamSubscription; + StreamSubscription? onLastTouchStreamSubscription; @override void initState() { @@ -142,11 +237,14 @@ class _NaverMapControllerExampleState extends State { onCameraChange(); onCameraChangeStreamSubscription = widget.onCameraChangeStream.listen((_) => onCameraChange()); + onLastTouchStreamSubscription = + widget.onLastTouchStream.listen(onLastTouch); } @override void dispose() { onCameraChangeStreamSubscription?.cancel(); + onLastTouchStreamSubscription?.cancel(); super.dispose(); } } diff --git a/example/lib/pages/examples/overlay_example.dart b/example/lib/pages/examples/overlay_example.dart index 994f377c..921c6d8b 100644 --- a/example/lib/pages/examples/overlay_example.dart +++ b/example/lib/pages/examples/overlay_example.dart @@ -3,15 +3,20 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_naver_map/flutter_naver_map.dart'; import 'package:flutter_naver_map_example/design/theme.dart'; -import 'package:flutter_naver_map_example/pages/example_base.dart'; +import 'package:flutter_naver_map_example/pages/utils/example_base.dart'; +import 'package:flutter_naver_map_example/util/overlay_portal_util.dart'; +import 'package:flutter_naver_map_example/util/string_util.dart'; import '../../design/custom_widget.dart'; class NOverlayExample extends ExampleBasePage { + final NInfoOverlayPortalController nOverlayInfoOverlayPortalController; + const NOverlayExample({ Key? key, required super.mapController, required super.canScroll, + required this.nOverlayInfoOverlayPortalController, }) : super(key: key); @override @@ -30,49 +35,66 @@ class _NOverlayExampleState extends State { type: willCreateOverlayType, cameraPosition: cameraPosition); overlay.setOnTapListener((overlay) { final latLng = cameraPosition.target; - mapController.latLngToScreenLocation(latLng).then( - (point) => addFlutterFloatingOverlay(point: point, overlay: overlay)); + mapController.latLngToScreenLocation(latLng).then((point) => + addFlutterFloatingOverlay( + point: point, overlay: overlay, latLng: latLng)); }); mapController.addOverlay(overlay); } - OverlayEntry? entry; - void addFlutterFloatingOverlay({ required NOverlay overlay, required NPoint point, + required NLatLng latLng, }) { - entry = OverlayEntry(builder: (context) { - final xPosition = (point.x) - 28; - return Positioned.fill( - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => removeFlutterFloatingOverlay(), - child: Stack(children: [ - Positioned( - left: xPosition < 0 ? 0 : xPosition, - top: point.y < 0 ? 0 : point.y, - child: Balloon( - size: const Size(200, 200), - padding: const EdgeInsets.all(8), - backgroundColor: getColorTheme(context).background, - child: ListView(children: [ - Text(overlay.toString()), - SimpleButton( - text: "지우기", - action: () { - mapController.deleteOverlay(overlay.info); - removeFlutterFloatingOverlay(); - }) - ]))) - ]))); - }); - Overlay.of(context).insert(entry!); - } + widget.nOverlayInfoOverlayPortalController.openWithWidget( + builder: (context, mapController, controller) { + Widget header() => Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text(overlay.runtimeType.toString(), + maxLines: 1, + softWrap: false, + overflow: TextOverflow.fade, + style: getTextTheme(context).titleSmall)), + const Icon(Icons.close_rounded), + ])); - void removeFlutterFloatingOverlay() { - entry?.remove(); - entry = null; + return Column(children: [ + header(), + Expanded( + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 10), + children: [ + Text( + overlay.info.id == overlay.info.parseIdAsTimeString() + ? "id: ${overlay.info.id}" + : "${overlay.info.parseIdAsTimeString().replaceFirst(":", "시 ").replaceFirst(":", "분 ")}초에 생성됨", + style: getTextTheme(context).bodySmall), + Text("${latLng.toShortString()}에 위치함", + style: getTextTheme(context).bodySmall), + const SizedBox(height: 4), + Text( + "zIndex: ${overlay.zIndex} (global: ${overlay.globalZIndex})\n" + "${overlay.minZoom} ${overlay.isMinZoomInclusive ? "≤" : "<"}" + " [보이는 줌 범위] ${overlay.isMaxZoomInclusive ? "≤" : "<"} ${overlay.maxZoom}\n", + style: getTextTheme(context).bodySmall), + ])), + SmallButton("오버레이 지우기", + icon: Icons.delete_forever_outlined, + radius: 0, + color: Colors.red.shade600, onTap: () { + mapController.deleteOverlay(overlay.info); + controller.hide(); + }), + ]); + }, + screenPoint: point, + overlay: overlay); } @override @@ -99,56 +121,53 @@ class _NOverlayExampleState extends State { padding: const EdgeInsets.fromLTRB(24, 0, 24, 12), child: Row(children: [ Expanded( - child: SimpleButton( - text: "${willCreateOverlayType.koreanName}만 모두 지우기", - color: Colors.orange, - margin: EdgeInsets.zero, - action: () => mapController.clearOverlays( - type: willCreateOverlayType)), - ), + child: SimpleButton( + text: "${willCreateOverlayType.koreanName}만 모두 지우기", + color: Colors.orange, + margin: EdgeInsets.zero, + action: () => mapController.clearOverlays( + type: willCreateOverlayType))), const SizedBox(width: 12), Expanded( - child: SimpleButton( - text: "모두 지우기", - color: Colors.red, - margin: EdgeInsets.zero, - action: () => mapController.clearOverlays()), - ), + child: SimpleButton( + text: "모두 지우기", + color: Colors.red, + margin: EdgeInsets.zero, + action: () => mapController.clearOverlays())), ])), const BottomPadding(), ]); } +} - @override - void dispose() { - removeFlutterFloatingOverlay(); - super.dispose(); +extension NOverlayTypeExtension on NOverlayType { + String get koreanName { + return switch (this) { + NOverlayType.marker => "마커", + NOverlayType.infoWindow => "정보창", + NOverlayType.circleOverlay => "원 오버레이", + NOverlayType.groundOverlay => "지상 오버레이", + NOverlayType.polygonOverlay => "다각형 오버레이", + NOverlayType.polylineOverlay => "선 오버레이", + NOverlayType.pathOverlay => "경로 오버레이", + NOverlayType.multipartPathOverlay => "경로(멀티파트) 오버레이", + NOverlayType.arrowheadPathOverlay => "경로(화살표) 오버레이", + NOverlayType.locationOverlay => "위치 오버레이", + }; } } -extension NOverlayExtension on NOverlayType { - String get koreanName { - switch (this) { - case NOverlayType.marker: - return "마커"; - case NOverlayType.infoWindow: - return "정보창"; - case NOverlayType.circleOverlay: - return "원 오버레이"; - case NOverlayType.groundOverlay: - return "지상 오버레이"; - case NOverlayType.polygonOverlay: - return "다각형 오버레이"; - case NOverlayType.polylineOverlay: - return "선 오버레이"; - case NOverlayType.pathOverlay: - return "경로 오버레이"; - case NOverlayType.multipartPathOverlay: - return "경로(멀티파트) 오버레이"; - case NOverlayType.arrowheadPathOverlay: - return "경로(화살표) 오버레이"; - case NOverlayType.locationOverlay: - return "위치 오버레이"; +extension NOverlayInfoExtension on NOverlayInfo { + String parseIdAsTimeString() { + final idForCreatedAt = int.tryParse(id); + if (idForCreatedAt == null) return id; + try { + return DateTime.fromMillisecondsSinceEpoch(idForCreatedAt) + .toIso8601String() + .split("T") + .last; + } catch (_) { + return id; } } } diff --git a/example/lib/pages/examples/pick_example.dart b/example/lib/pages/examples/pick_example.dart new file mode 100644 index 00000000..574429b4 --- /dev/null +++ b/example/lib/pages/examples/pick_example.dart @@ -0,0 +1,122 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_naver_map/flutter_naver_map.dart'; +import 'package:flutter_naver_map_example/design/custom_widget.dart'; +import 'package:flutter_naver_map_example/pages/utils/example_base.dart'; +import 'package:flutter_naver_map_example/pages/examples/overlay_example.dart'; + +class NaverMapPickExample extends ExampleBasePage { + final Stream onCameraChangeStream; + final Point mapEndPoint; + + const NaverMapPickExample({ + super.key, + required super.mapController, + required super.canScroll, + required this.mapEndPoint, + required this.onCameraChangeStream, + }); + + @override + State createState() => _NaverMapControllerExampleState(); +} + +class _NaverMapControllerExampleState extends State { + final List pickables = []; + + void onCameraChange() async { + final p = widget.mapEndPoint; + final center = NPoint(p.x / 2, p.y / 2); + final radius = max(p.x, p.y); + final pickables = await _mapController.pickAll(center, radius: radius); + this.pickables + ..clear() + ..addAll(pickables); + setState(() {}); + } + + Widget _nowCameraPositionWidget() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: + Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + const InnerSimpleTitle( + title: "지도에 표시되는 심볼 및 오버레이들", + description: "controller.pickAll(NPoint, radius)", + direction: Axis.vertical), + const SizedBox(height: 8), + Expanded( + child: SingleChildScrollView( + padding: EdgeInsets.only( + bottom: 8 + MediaQuery.paddingOf(context).bottom), + child: HalfActionButtonGrid( + buttons: pickables.map((e) => _pickableItem(e)).toList()), + )), + ])); + } + + Widget _pickableItem(NPickableInfo info) { + final String title; + final String description; + final IconData icon; + final void Function() action; + + if (info is NOverlayInfo) { + title = info.type.koreanName; + description = info.parseIdAsTimeString(); + + icon = switch (info.type) { + NOverlayType.marker => Icons.place_rounded, + NOverlayType.infoWindow => Icons.chat_bubble_rounded, + NOverlayType.circleOverlay => Icons.circle_outlined, + NOverlayType.groundOverlay => Icons.square, + NOverlayType.polygonOverlay => Icons.star, + NOverlayType.polylineOverlay => Icons.polyline_rounded, + NOverlayType.pathOverlay => Icons.route_rounded, + NOverlayType.multipartPathOverlay => Icons.route_sharp, + NOverlayType.arrowheadPathOverlay => Icons.arrow_right_alt_rounded, + NOverlayType.locationOverlay => Icons.my_location_rounded, + }; + action = () {}; + } else if (info is NSymbolInfo) { + title = info.caption.replaceAll("\n", " "); + description = + "${info.position.latitude.toStringAsFixed(5)}, ${info.position.longitude.toStringAsFixed(5)}"; + icon = Icons.apartment_rounded; + action = () => _mapController + .updateCamera(NCameraUpdate.scrollAndZoomTo(target: info.position)); + } else { + return const SizedBox(); + } + + return HalfActionButton( + action: action, icon: icon, title: title, description: description); + } + + @override + Widget build(BuildContext context) { + return Expanded(child: _nowCameraPositionWidget()); + } + + // --- worker --- + + NaverMapController get _mapController => widget.mapController; + StreamSubscription? onCameraChangeStreamSubscription; + + @override + void initState() { + super.initState(); + onCameraChange(); + onCameraChangeStreamSubscription = + widget.onCameraChangeStream.listen((_) => onCameraChange()); + } + + @override + void dispose() { + onCameraChangeStreamSubscription?.cancel(); + onCameraChangeStreamSubscription = null; + super.dispose(); + } +} diff --git a/example/lib/pages/utils/bottom_drawer.dart b/example/lib/pages/utils/bottom_drawer.dart new file mode 100644 index 00000000..06c2f10d --- /dev/null +++ b/example/lib/pages/utils/bottom_drawer.dart @@ -0,0 +1,162 @@ +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bottom_drawer/flutter_bottom_drawer.dart'; +import 'package:flutter_naver_map_example/design/custom_widget.dart'; + +import '../../design/map_function_item.dart'; +import '../../design/theme.dart'; + +class ExampleAppBottomDrawer { + final BuildContext context; + final Function(double height) onDrawerHeightChanged; + final Function() rebuild; + final List pages; + final void Function()? onPageDispose; + + ExampleAppBottomDrawer({ + required this.context, + required this.onDrawerHeightChanged, + required this.rebuild, + required this.pages, + this.onPageDispose, + }); + + ColorScheme get colorTheme => getColorTheme(context); + + TextTheme get textTheme => getTextTheme(context); + + late DrawerMoveController drawerController; + late DrawerState drawerState; + late Function(Function()) drawerSetState; + + MapFunctionItem? nowItem; + + bool get hasPage => nowItem != null; + + void go(MapFunctionItem item) { + nowItem = item; + if (drawerController.nowState == DrawerState.closed) { + drawerController.open(); + Future.delayed(_drawerAnimationDuration) + .then((_) => rebuildDrawerAndPage()); + } else { + rebuildDrawerAndPage(); + } + } + + void back() { + nowItem = null; + onPageDispose?.call(); + rebuildDrawerAndPage(); + if (drawerController.nowState == DrawerState.opened) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + drawerController.close(); + Future.delayed(_drawerAnimationDuration) + .then((_) => rebuildDrawerAndPage()); + }); + } + } + + void rebuildDrawerAndPage() { + drawerSetState(() {}); // drawer rebuild + rebuild(); // page rebuild (height changed) + } + + static const _drawerAnimationDuration = Duration(milliseconds: 300); + + BottomDrawer get bottomDrawer => BottomDrawer( + height: nowItem?.isScrollPage == true + ? MediaQuery.sizeOf(context).height / 3.6 + : null, + expandedHeight: 480, + handleSectionHeight: 20, + handleColor: colorTheme.secondary, + backgroundColor: colorTheme.background, + onReady: (controller) => drawerController = controller, + onStateChanged: (state) => drawerState = state, + onHeightChanged: onDrawerHeightChanged, + resizeAnimationDuration: _drawerAnimationDuration, + builder: (state, setState, context) { + drawerSetState = setState; + return hasPage + ? selectedPage(state == DrawerState.opened) + : innerListView(state == DrawerState.closed); + }); + + Widget innerListView(bool isClosed) => Column(children: [ + innerListViewHeader(), + Expanded( + flex: isClosed ? 0 : 1, + child: Padding( + padding: const EdgeInsets.all(8) + .copyWith(bottom: 8 + MediaQuery.of(context).padding.bottom), + child: HalfActionButtonGrid( + buttons: pages.map((page) { + if (page.needItemOnTap) { + page = page.copyWith( + onTap: go, + onBack: back, + drawerController: drawerController); + } + return page.getItemWidget(context); + }).toList()), + ), + ), + ]); + + Widget innerListViewHeader() => Container( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: colorTheme.onBackground.withOpacity(0.28), width: 0.2)), + ), + padding: const EdgeInsets.fromLTRB(24, 12, 24, 16), + child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + Text("지도 기능 둘러보기", style: textTheme.titleLarge), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) => + Row(mainAxisAlignment: MainAxisAlignment.end, children: [ + Container( + padding: + const EdgeInsets.symmetric(vertical: 2, horizontal: 4), + decoration: BoxDecoration( + color: Platform.isAndroid ? Colors.green : Colors.black, + borderRadius: BorderRadius.circular(4)), + child: Row(children: [ + Icon(Platform.isAndroid ? Icons.android : Icons.apple, + color: Colors.white, size: 14), + const SizedBox(width: 2), + ConstrainedBox( + constraints: + BoxConstraints(maxWidth: constraints.maxWidth - 32), + child: Text(Platform.operatingSystemVersion, + softWrap: false, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: textTheme.labelSmall), + ), + ])), + ]), + ), + ), + ])); + + Widget selectedPage(bool canScroll) { + assert(nowItem != null); + return nowItem!.getPageWidget(context, canScroll: canScroll); + } + + bool processWillPop() { + if (hasPage) { + back(); + return false; + } else if (drawerState != DrawerState.closed) { + drawerController.close(); + return false; + } + return true; + } +} diff --git a/example/lib/pages/example_base.dart b/example/lib/pages/utils/example_base.dart similarity index 99% rename from example/lib/pages/example_base.dart rename to example/lib/pages/utils/example_base.dart index 7bc7c08d..73a7c47b 100644 --- a/example/lib/pages/example_base.dart +++ b/example/lib/pages/utils/example_base.dart @@ -11,4 +11,3 @@ abstract class ExampleBasePage extends StatefulWidget { required this.canScroll, }); } - diff --git a/example/lib/pages/new_window_page.dart b/example/lib/pages/utils/new_window_page.dart similarity index 100% rename from example/lib/pages/new_window_page.dart rename to example/lib/pages/utils/new_window_page.dart diff --git a/example/lib/util/overlay_portal_util.dart b/example/lib/util/overlay_portal_util.dart new file mode 100644 index 00000000..16a5a658 --- /dev/null +++ b/example/lib/util/overlay_portal_util.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_naver_map/flutter_naver_map.dart'; +import 'package:flutter_naver_map_example/design/custom_widget.dart'; +import 'package:flutter_naver_map_example/design/theme.dart'; + +typedef NInfoOverlayBuilderCreateCallback = Widget Function( + BuildContext context, + NaverMapController mapController, + NInfoOverlayPortalController controller); + +typedef NInfoOverlayBuilderCallback = Widget Function( + BuildContext context, NaverMapController mapController); + +class NInfoOverlayPortalController extends OverlayPortalController { + NInfoOverlayPortalController({super.debugLabel}); + + NInfoOverlayBuilderCallback _builder = + (BuildContext _, NaverMapController __) => const SizedBox(); + + NInfoOverlayBuilderCallback get builder => _builder; + + void openWithWidget({ + required NInfoOverlayBuilderCreateCallback builder, + required NPoint screenPoint, + required NOverlay overlay, + }) { + _builder = (BuildContext context, NaverMapController mapController) => + _nOverlayInfoOverlayWithTouchScope(context, + point: screenPoint, + child: builder.call(context, mapController, this)); + show(); + } + + Widget _nOverlayInfoOverlayWithTouchScope(BuildContext context, + {required NPoint point, required Widget child}) { + final xPosition = (point.x) - 28; + return Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => hide(), + child: Container( + color: Colors.black12, + child: Stack(children: [ + Positioned( + left: xPosition < 0 ? 0 : xPosition, + top: point.y < 0 ? 0 : point.y, + child: Balloon( + size: const Size(200, 160), + padding: EdgeInsets.zero, + backgroundColor: getColorTheme(context).background, + child: child)), + ])))); + } +} diff --git a/example/lib/util/string_util.dart b/example/lib/util/string_util.dart new file mode 100644 index 00000000..e21538fb --- /dev/null +++ b/example/lib/util/string_util.dart @@ -0,0 +1,7 @@ +import 'package:flutter_naver_map/flutter_naver_map.dart'; + +extension NLatLngExtension on NLatLng { + String toShortString([int length = 5]) { + return "${latitude.toStringAsFixed(length)}, ${longitude.toStringAsFixed(length)}"; + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock index 57d1b8a5..92472df0 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -82,10 +82,10 @@ packages: dependency: "direct main" description: name: flutter_bottom_drawer - sha256: d84e9bd2a99ec7879ab238e1886a9e0f723a402942ea9ce9e1cf60c602f2a9dd + sha256: "3d832e1819210144e9a0ce30a574acecda1a2ad6a61fcf6898f4fb1a82024693" url: "https://pub.dev" source: hosted - version: "0.0.8" + version: "0.1.0" flutter_driver: dependency: transitive description: flutter @@ -110,7 +110,7 @@ packages: path: ".." relative: true source: path - version: "1.1.0" + version: "1.1.1" flutter_styled_toast: dependency: "direct main" description: @@ -124,6 +124,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -234,42 +239,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "284a66179cabdf942f838543e10413246f06424d960c92ba95c84439154fcac8" + sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78" url: "https://pub.dev" source: hosted - version: "11.0.1" + version: "11.1.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: f9fddd3b46109bd69ff3f9efa5006d2d309b7aec0f3c1c5637a60a2d5659e76e + sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6" url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "12.0.1" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306" url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "9.2.0" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df" + url: "https://pub.dev" + source: hosted + version: "0.1.0+2" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" + sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1 url: "https://pub.dev" source: hosted - version: "3.12.0" + version: "4.0.2" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004" url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.0" platform: dependency: transitive description: @@ -363,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + transparent_pointer: + dependency: "direct main" + description: + name: transparent_pointer + sha256: "27f5a7a63e517b6a56962bd473bbfcdcacce13fc996a264d6665da9a24650eb9" + url: "https://pub.dev" + source: hosted + version: "1.0.0" typed_data: dependency: transitive description: @@ -421,4 +442,4 @@ packages: version: "1.0.3" sdks: dart: ">=3.2.1 <4.0.0" - flutter: ">=3.10.0" + flutter: ">=3.16.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 538a9386..00dfadac 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,10 +15,11 @@ dependencies: flutter_naver_map: path: ../ - flutter_bottom_drawer: 0.0.8 - permission_handler: 11.0.1 + flutter_bottom_drawer: 0.1.0 + permission_handler: 11.1.0 flutter_styled_toast: 2.2.1 pull_to_refresh_flutter3: 2.0.2 + transparent_pointer: ^1.0.0 dev_dependencies: flutter_test: diff --git a/ios/Classes/controller/overlay/OverlayController.swift b/ios/Classes/controller/overlay/OverlayController.swift index cc3915a5..7eb8ec8b 100644 --- a/ios/Classes/controller/overlay/OverlayController.swift +++ b/ios/Classes/controller/overlay/OverlayController.swift @@ -233,7 +233,8 @@ internal class OverlayController: OverlaySender, OverlayHandler, ArrowheadPathOv } func openInfoWindow(_ marker: NMFMarker, rawInfoWindow: Any, rawAlign: Any, success: (Any?) -> ()) { - let nInfoWindow = NInfoWindow.fromMessageable(rawInfoWindow) + var nInfoWindow = NInfoWindow.fromMessageable(rawInfoWindow) + nInfoWindow.setCommonProperties(rawArgs: asDict(rawInfoWindow)) let infoWindow = saveOverlayWithAddable(creator: nInfoWindow) as! NMFInfoWindow let align = try! asAlign(rawAlign) diff --git a/lib/src/controller/map/sender.dart b/lib/src/controller/map/sender.dart index 61f4317b..eb6d12da 100644 --- a/lib/src/controller/map/sender.dart +++ b/lib/src/controller/map/sender.dart @@ -25,10 +25,8 @@ abstract class _NaverMapControlSender { /// using parameter is deprecated. use [getMeterPerDpAtLatitude] instead. /// please getMeterPerDp() without parameter. Future getMeterPerDp({ - @Deprecated("use getMeterPerDpAtLatitude() instead") - double? latitude, - @Deprecated("use getMeterPerDpAtLatitude() instead") - double? zoom, + @Deprecated("use getMeterPerDpAtLatitude() instead") double? latitude, + @Deprecated("use getMeterPerDpAtLatitude() instead") double? zoom, }); /// meter / dp at latitude (required zoomLevel) diff --git a/lib/src/type/default/padding.dart b/lib/src/type/default/padding.dart index de6bc495..5d2965e9 100644 --- a/lib/src/type/default/padding.dart +++ b/lib/src/type/default/padding.dart @@ -1,8 +1,7 @@ part of flutter_naver_map; class NEdgeInsets extends EdgeInsets with NMessageableWithMap { - const NEdgeInsets.fromLTRB( - super.left, super.top, super.right, super.bottom) + const NEdgeInsets.fromLTRB(super.left, super.top, super.right, super.bottom) : super.fromLTRB(); NEdgeInsets.fromEdgeInsets(EdgeInsets insets) diff --git a/lib/src/type/map/overlay/overlay_image.dart b/lib/src/type/map/overlay/overlay_image.dart index 03dbe88e..21d524f2 100644 --- a/lib/src/type/map/overlay/overlay_image.dart +++ b/lib/src/type/map/overlay/overlay_image.dart @@ -28,6 +28,10 @@ class NOverlayImage with NMessageableWithMap { required Size size, required BuildContext context, }) async { + assert( + widget.runtimeType != Image, + "Do not use Image widget.\n" + "Instead, using `NOverlayImage.fromAssetImage` or `.fromFile` or `.fromByteArray` Constructor."); final imageBytes = await WidgetToImageUtil.widgetToImageByte(widget, size: size, context: context); final path = await ImageUtil.saveImage(imageBytes); diff --git a/lib/src/widget/platform_view.dart b/lib/src/widget/platform_view.dart index 6901f977..c76a28e4 100644 --- a/lib/src/widget/platform_view.dart +++ b/lib/src/widget/platform_view.dart @@ -21,20 +21,13 @@ class _PlatformViewCreator { hitTestBehavior: hitTestBehavior, ), onCreatePlatformView: (params) { - // issues - // 1. android 5.0 (API 21) ~ android 7.1 (API 25) render issue (GLSurfaceView rendered under flutter view) - // -> ExpensiveAndroidView, TextureSurfaceComposition (SurfaceAndroidView). - // 2. "Unexpected platform view context for view ID 0;" issue. (+ minimum API 23) - // -> AndroidView (virtual display) + // RenderView(Impl Android Side), Display Mode + // API 23 ~ 29 : TextureView, Texture Layer Hybrid Composition. + // API 30 ~ : GLSurfaceView, Hybrid Composition (TLHC cause issue: flutter#98865) - // initAndroidView : auto detect. - // but Android 10 or higher, use TextureSurfaceComposition. - - const hybridView = PlatformViewsService.initExpensiveAndroidView; - // const autoView = PlatformViewsService.initAndroidView; - const autoView = PlatformViewsService.initSurfaceAndroidView; - - final usingView = androidSdkVersion! >= 26 ? hybridView : autoView; + final usingView = androidSdkVersion! <= 29 + ? PlatformViewsService.initAndroidView + : PlatformViewsService.initExpensiveAndroidView; final view = usingView.call( id: params.id, diff --git a/pubspec.yaml b/pubspec.yaml index 79d6d337..797be04d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_naver_map description: Naver Map plugin for Flutter, which provides map service of Korea. -version: 1.1.0 +version: 1.1.1 homepage: https://github.com/note11g/flutter_naver_map documentation: https://note11.dev/flutter_naver_map