Compare commits
80 Commits
756bf53cf5
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| c6e7616be2 | |||
| eae654c47e | |||
| f01875abb9 | |||
| 881da166d1 | |||
| 14b2e6b35c | |||
| 20c39a4a12 | |||
|
|
5c74c7ccc0 | ||
| 23fc0da5c8 | |||
| 7efd933416 | |||
| 2b33dac384 | |||
| ef60b8ed62 | |||
| bec6952707 | |||
| f68349a208 | |||
|
|
08dcc64ef4 | ||
| 9242dcaf70 | |||
| 95fdfe6269 | |||
| ee88b3ada9 | |||
| ba27799c41 | |||
| c527732532 | |||
| cd223fa9bf | |||
| fdd6653fce | |||
| 8748a60f7f | |||
|
|
5843ef6e87 | ||
| 0123025296 | |||
| 587dd48896 | |||
| 569780ffb4 | |||
| 3aac4e7330 | |||
| b275c66383 | |||
| bf80931f37 | |||
| 67c36f436c | |||
| c10c92afdc | |||
| d3edf95df3 | |||
| 930953780d | |||
| a497bc6469 | |||
| 3f282d15c1 | |||
| 03b35f660c | |||
| 9b6f93ca95 | |||
| eb41ecaec4 | |||
| 384de27f2c | |||
| 84b174c4a5 | |||
| cd14469d79 | |||
| b846d352a2 | |||
|
|
a24f41a8d5 | ||
|
|
65b4a3ac34 | ||
| 02e1946319 | |||
| d5083c1939 | |||
| fe44848529 | |||
| 572c416827 | |||
| 8df16f0787 | |||
| ce6bd3edd2 | |||
| 6997b4ac9e | |||
| a26d2478f3 | |||
| 0dded3b928 | |||
| b7caf58adf | |||
| 0df1902df2 | |||
| a8314d8a7a | |||
| 39cae906e9 | |||
| 14fd6c75d0 | |||
| 1724852a39 | |||
| a05d4ebb9b | |||
| 600cea4379 | |||
| 3dfc1dfc2c | |||
| 909dc95771 | |||
| cf0896453b | |||
| dce9718320 | |||
| 4491aa9b91 | |||
| 5364612a6f | |||
| 10867178fa | |||
| a5e2a89e4f | |||
| 26c5f9d67a | |||
| 9cd87b0535 | |||
| 45e45d8160 | |||
| 87e890f97e | |||
| dcf925b8c1 | |||
| c45863eda6 | |||
| 35bd3a78a5 | |||
| 032e60d362 | |||
| 45f5035d1b | |||
| f792915429 | |||
| edbacc502b |
8
ln_jq_app/.claude/settings.local.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(flutter build apk --debug 2>&1 | tail -15)",
|
||||
"Bash(flutter build apk --debug 2>&1 | tail -10)"
|
||||
]
|
||||
}
|
||||
}
|
||||
7
ln_jq_app/android/.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(./gradlew assembleDebug --stacktrace)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ android {
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = 6
|
||||
versionName = "1.2.3"
|
||||
versionCode = 7
|
||||
versionName = "1.2.4"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -68,3 +68,9 @@ android {
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.amap.api:navi-3dmap-location-search:10.0.700_3dmap10.0.700_loc6.4.5_sea9.7.2")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||
}
|
||||
|
||||
27
ln_jq_app/android/app/proguard-rules.pro
vendored
@@ -59,3 +59,30 @@
|
||||
-dontwarn anetwork.**
|
||||
-dontwarn com.ut.**
|
||||
-dontwarn com.ta.**
|
||||
|
||||
|
||||
#3D 地图 V5.0.0之前:
|
||||
-keep class com.amap.api.maps.**{*;}
|
||||
-keep class com.autonavi.amap.mapcore.*{*;}
|
||||
-keep class com.amap.api.trace.**{*;}
|
||||
|
||||
#3D 地图 V5.0.0之后:
|
||||
-keep class com.amap.api.maps.**{*;}
|
||||
-keep class com.autonavi.**{*;}
|
||||
-keep class com.amap.api.trace.**{*;}
|
||||
|
||||
#定位
|
||||
-keep class com.amap.api.location.**{*;}
|
||||
-keep class com.amap.api.fence.**{*;}
|
||||
-keep class com.autonavi.aps.amapapi.model.**{*;}
|
||||
|
||||
#搜索
|
||||
-keep class com.amap.api.services.**{*;}
|
||||
|
||||
#2D地图
|
||||
-keep class com.amap.api.maps2d.**{*;}
|
||||
-keep class com.amap.api.mapcore2d.**{*;}
|
||||
|
||||
#导航
|
||||
-keep class com.amap.api.navi.**{*;}
|
||||
-keep class com.autonavi.**{*;}
|
||||
@@ -1,11 +1,14 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
<uses-permission
|
||||
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
@@ -14,22 +17,54 @@
|
||||
<!--定位权限-->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||
<!--用于申请调用A-GPS模块-->
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission>
|
||||
<!--如果设置了target >= 28 如果需要启动后台定位则必须声明这个权限-->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!--如果您的应用需要后台定位权限,且有可能运行在Android Q设备上,并且设置了target>28,必须增加这个权限声明-->
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
|
||||
<application
|
||||
android:label="小羚羚"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/logo">
|
||||
android:icon="@mipmap/logo"
|
||||
android:label="小羚羚">
|
||||
|
||||
<!-- 高德地图Key -->
|
||||
<meta-data
|
||||
android:name="com.amap.api.v2.apikey"
|
||||
android:value="92495660f7bc990cb475426c47c03b65" />
|
||||
|
||||
<!-- 高德地图定位服务 -->
|
||||
<service android:name="com.amap.api.location.APSService" />
|
||||
<!--高德导航-->
|
||||
<activity
|
||||
android:name="com.amap.api.navi.AmapRouteActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|navigation" />
|
||||
<!--自定义导航 Activity-->
|
||||
<activity
|
||||
android:name="com.lnkj.ln_jq_app.NavigationActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|navigation"
|
||||
android:screenOrientation="portrait" />
|
||||
<!--搜索目的地 Activity-->
|
||||
<activity
|
||||
android:name="com.lnkj.ln_jq_app.SearchDestinationActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize|navigation"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:exported="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
@@ -37,8 +72,7 @@
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
android:resource="@style/NormalTheme" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -51,30 +85,13 @@
|
||||
android:value="2" />
|
||||
|
||||
<!-- 请填写你自己的- appKey -->
|
||||
<meta-data android:name="com.alibaba.app.appkey" android:value="335642645"/>
|
||||
<meta-data
|
||||
android:name="com.alibaba.app.appkey"
|
||||
android:value="335642645" />
|
||||
<!-- 请填写你自己的appSecret -->
|
||||
<meta-data android:name="com.alibaba.app.appsecret" android:value="39628204345a4240b5b645b68a5896c7"/>
|
||||
<!-- 华为通道的参数appid -->
|
||||
<meta-data android:name="com.huawei.hms.client.appid" android:value="" />
|
||||
|
||||
<!-- vivo通道的参数api_key为appkey -->
|
||||
<meta-data android:name="com.vivo.push.api_key" android:value="" />
|
||||
<meta-data android:name="com.vivo.push.app_id" android:value="" />
|
||||
|
||||
<!-- honor通道的参数-->
|
||||
<meta-data android:name="com.hihonor.push.app_id" android:value="" />
|
||||
|
||||
<!-- oppo -->
|
||||
<meta-data android:name="com.oppo.push.key" android:value="" />
|
||||
<meta-data android:name="com.oppo.push.secret" android:value="" />
|
||||
|
||||
<!-- 小米-->
|
||||
<meta-data android:name="com.xiaomi.push.id" android:value="id=2222222222222222222" />
|
||||
<meta-data android:name="com.xiaomi.push.key" android:value="id=5555555555555" />
|
||||
|
||||
<!-- 魅族-->
|
||||
<meta-data android:name="com.meizu.push.id" android:value="" />
|
||||
<meta-data android:name="com.meizu.push.key" android:value="" />
|
||||
<meta-data
|
||||
android:name="com.alibaba.app.appsecret"
|
||||
android:value="39628204345a4240b5b645b68a5896c7" />
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +116,7 @@
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
|
||||
@@ -1,6 +1,161 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import io.flutter.embedding.android.FlutterActivity;
|
||||
import io.flutter.embedding.engine.FlutterEngine;
|
||||
|
||||
public class MainActivity extends FlutterActivity {
|
||||
|
||||
private static final String CHANNEL = "com.lnkj.ln_jq_app/map";
|
||||
private static final String TAG = "MainActivity";
|
||||
|
||||
// 权限请求码
|
||||
private static final int PERMISSION_REQUEST_CODE = 1001;
|
||||
|
||||
private NativeMapView mapView;
|
||||
|
||||
@Override
|
||||
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine);
|
||||
|
||||
// 注册高德地图导航Platform View
|
||||
flutterEngine
|
||||
.getPlatformViewsController()
|
||||
.getRegistry()
|
||||
.registerViewFactory(
|
||||
"NativeFirstPage",
|
||||
new NativeMapFactory(this)
|
||||
);
|
||||
|
||||
// 设置 Activity 实例到地图
|
||||
NativeMapFactory.setActivity(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前系统版本需要申请的权限列表
|
||||
*/
|
||||
private String[] getRequiredPermissions() {
|
||||
List<String> permissions = new ArrayList<>();
|
||||
// 定位权限是必须的
|
||||
permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||
|
||||
// 存储权限处理:Android 13 (API 33) 以下才需要申请 legacy 存储权限
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
return permissions.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并申请权限
|
||||
*/
|
||||
private void checkAndRequestPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
String[] requiredPermissions = getRequiredPermissions();
|
||||
List<String> deniedPermissions = new ArrayList<>();
|
||||
|
||||
for (String permission : requiredPermissions) {
|
||||
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
deniedPermissions.add(permission);
|
||||
}
|
||||
}
|
||||
|
||||
if (!deniedPermissions.isEmpty()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
deniedPermissions.toArray(new String[0]),
|
||||
PERMISSION_REQUEST_CODE
|
||||
);
|
||||
} else {
|
||||
Log.d(TAG, "所有必要权限已授予");
|
||||
if (mapView != null) {
|
||||
mapView.startLocation();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mapView != null) {
|
||||
mapView.startLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
public void setMapView(NativeMapView mapView) {
|
||||
this.mapView = mapView;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mapView != null) {
|
||||
mapView.dispose();
|
||||
mapView = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
// 处理从搜索目的地页面返回的结果
|
||||
if (requestCode == 1000 && resultCode == RESULT_OK && data != null) {
|
||||
String name = data.getStringExtra(SearchDestinationActivity.EXTRA_RESULT_NAME);
|
||||
String address = data.getStringExtra(SearchDestinationActivity.EXTRA_RESULT_ADDRESS);
|
||||
double lat = data.getDoubleExtra(SearchDestinationActivity.EXTRA_RESULT_LAT, 0);
|
||||
double lon = data.getDoubleExtra(SearchDestinationActivity.EXTRA_RESULT_LON, 0);
|
||||
String district = data.getStringExtra(SearchDestinationActivity.EXTRA_RESULT_DISTRICT);
|
||||
|
||||
if (name != null && lat != 0 && lon != 0) {
|
||||
// 获取地图实例并设置目的地
|
||||
NativeMapView mapView = NativeMapFactory.getMapView();
|
||||
if (mapView != null) {
|
||||
mapView.setDestination(name, address, lat, lon, district);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (requestCode == PERMISSION_REQUEST_CODE) {
|
||||
boolean locationGranted = false;
|
||||
for (int i = 0; i < permissions.length; i++) {
|
||||
if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i])
|
||||
&& grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||
locationGranted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (locationGranted) {
|
||||
if (mapView != null) {
|
||||
mapView.startLocation();
|
||||
}
|
||||
} else {
|
||||
// 只有在定位权限确实被拒绝时才弹出提示
|
||||
Toast.makeText(this, "请授予应用定位权限以正常使用地图功能", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import io.flutter.plugin.common.MessageCodec;
|
||||
import io.flutter.plugin.common.StandardMessageCodec;
|
||||
import io.flutter.plugin.platform.PlatformView;
|
||||
import io.flutter.plugin.platform.PlatformViewFactory;
|
||||
|
||||
/**
|
||||
* 高德地图导航 Platform View Factory
|
||||
* 对应iOS的NativeViewFactory
|
||||
*/
|
||||
public class NativeMapFactory extends PlatformViewFactory {
|
||||
|
||||
private static final String VIEW_TYPE_ID = "NativeFirstPage";
|
||||
private static NativeMapView mapViewInstance = null;
|
||||
private final Context context;
|
||||
|
||||
public NativeMapFactory(Context context) {
|
||||
super(StandardMessageCodec.INSTANCE);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformView create(Context context, int viewId, Object args) {
|
||||
mapViewInstance = new NativeMapView(context, viewId, args);
|
||||
return mapViewInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地图实例,供MainActivity使用
|
||||
*/
|
||||
public static NativeMapView getMapView() {
|
||||
return mapViewInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Activity 实例
|
||||
*/
|
||||
public static void setActivity(Activity activity) {
|
||||
if (mapViewInstance != null) {
|
||||
mapViewInstance.setActivity(activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.amap.api.maps.AMapException;
|
||||
import com.amap.api.navi.AMapNavi;
|
||||
import com.amap.api.navi.AMapNaviListener;
|
||||
import com.amap.api.navi.AMapNaviView;
|
||||
import com.amap.api.navi.AMapNaviViewListener;
|
||||
import com.amap.api.navi.AMapNaviViewOptions;
|
||||
import com.amap.api.navi.enums.NaviType;
|
||||
import com.amap.api.navi.enums.PathPlanningStrategy;
|
||||
import com.amap.api.navi.model.AMapCalcRouteResult;
|
||||
import com.amap.api.navi.model.AMapCarInfo;
|
||||
import com.amap.api.navi.model.AMapLaneInfo;
|
||||
import com.amap.api.navi.model.AMapModelCross;
|
||||
import com.amap.api.navi.model.AMapNaviCameraInfo;
|
||||
import com.amap.api.navi.model.AMapNaviCross;
|
||||
import com.amap.api.navi.model.AMapNaviLocation;
|
||||
import com.amap.api.navi.model.AMapNaviRouteNotifyData;
|
||||
import com.amap.api.navi.model.AMapNaviTrafficFacilityInfo;
|
||||
import com.amap.api.navi.model.AMapServiceAreaInfo;
|
||||
import com.amap.api.navi.model.AimLessModeCongestionInfo;
|
||||
import com.amap.api.navi.model.AimLessModeStat;
|
||||
import com.amap.api.navi.model.NaviInfo;
|
||||
import com.amap.api.navi.model.NaviPoi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.activity.ComponentActivity;
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* 导航
|
||||
*/
|
||||
public class NavigationActivity extends ComponentActivity implements AMapNaviListener {
|
||||
|
||||
private static final String TAG = "NavigationActivity";
|
||||
|
||||
private AMapNavi mAMapNavi;
|
||||
private AMapNaviView naviView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// 创建导航视图
|
||||
naviView = new AMapNaviView(this);
|
||||
naviView.onCreate(savedInstanceState);
|
||||
naviView.setAMapNaviViewListener(new AMapNaviViewListener() {
|
||||
@Override
|
||||
public void onNaviSetting() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviCancel() {
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNaviBackClick() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviMapMode(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviTurnClick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNextRoadClick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScanViewButtonClick() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockMap(boolean b) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviViewLoaded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMapTypeChanged(int i) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviViewShowMode(int i) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
AMapNaviViewOptions aMapNaviViewOptions = new AMapNaviViewOptions();
|
||||
Bitmap carBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.car);
|
||||
aMapNaviViewOptions.setCarBitmap(carBitmap);
|
||||
aMapNaviViewOptions.setSettingMenuEnabled(false);
|
||||
naviView.setViewOptions(aMapNaviViewOptions);
|
||||
|
||||
// 创建 AMapNavi 实例
|
||||
try {
|
||||
mAMapNavi = AMapNavi.getInstance(this);
|
||||
mAMapNavi.addAMapNaviListener(this);
|
||||
} catch (AMapException e) {
|
||||
Log.e(TAG, "初始化导航失败", e);
|
||||
Toast.makeText(this, "初始化导航失败", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到容器
|
||||
FrameLayout container = new FrameLayout(this);
|
||||
container.addView(naviView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
setContentView(container);
|
||||
|
||||
// 获取 Intent 中的参数
|
||||
processIntent(getIntent());
|
||||
|
||||
// 处理返回键
|
||||
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
Log.d(TAG, "NavigationActivity created");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
processIntent(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 Intent 参数并启动导航
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void processIntent(Intent intent) {
|
||||
if (intent == null) {
|
||||
Log.w(TAG, "Intent is null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取起点、终点和途经点
|
||||
NaviPoi startNaviPoi = (NaviPoi) intent.getParcelableExtra("startPoi");
|
||||
NaviPoi endNaviPoi = (NaviPoi) intent.getParcelableExtra("endPoi");
|
||||
List<NaviPoi> wayPoints = intent.getParcelableArrayListExtra("wayPoints");
|
||||
|
||||
if (wayPoints != null) {
|
||||
Log.d("Navi", "获取到途径点数量: " + wayPoints.size());
|
||||
}
|
||||
|
||||
|
||||
if (startNaviPoi == null || endNaviPoi == null) {
|
||||
Log.e(TAG, "Missing start or end point");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置车辆信息
|
||||
AMapCarInfo carInfo = (AMapCarInfo) intent.getParcelableExtra("carInfo");
|
||||
if (carInfo != null) {
|
||||
mAMapNavi.setCarInfo(carInfo);
|
||||
}
|
||||
|
||||
mAMapNavi.setUseInnerVoice(true);
|
||||
// 计算并启动导航
|
||||
mAMapNavi.calculateDriveRoute(startNaviPoi, endNaviPoi, wayPoints, PathPlanningStrategy.DRIVING_MULTIPLE_ROUTES_DEFAULT);
|
||||
|
||||
Log.d(TAG, "Navigation started from: " + startNaviPoi.getName() + " - " + endNaviPoi.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Process intent error", e);
|
||||
Toast.makeText(this, "导航启动失败", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (naviView != null) {
|
||||
naviView.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (naviView != null) {
|
||||
naviView.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (naviView != null) {
|
||||
naviView.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 停止导航
|
||||
if (mAMapNavi != null) {
|
||||
mAMapNavi.stopNavi();
|
||||
mAMapNavi.removeAMapNaviListener(this);
|
||||
}
|
||||
if (naviView != null) {
|
||||
naviView.onDestroy();
|
||||
}
|
||||
Log.d(TAG, "NavigationActivity destroyed");
|
||||
}
|
||||
|
||||
private int dp2px(float dp) {
|
||||
float density = getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * density + 0.5f);
|
||||
}
|
||||
|
||||
// ============ AMapNaviListener 回调实现 ============
|
||||
|
||||
|
||||
@Override
|
||||
public void onInitNaviFailure() {
|
||||
Log.e(TAG, "onInitNaviFailure");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitNaviSuccess() {
|
||||
Log.d(TAG, "onInitNaviSuccess");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartNavi(int i) {
|
||||
Log.d(TAG, "onStartNavi: " + i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTrafficStatusUpdate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChange(AMapNaviLocation aMapNaviLocation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetNavigationText(int i, String s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetNavigationText(String s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEndEmulatorNavi() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArriveDestination() {
|
||||
Log.d(TAG, "onArriveDestination");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteFailure(int i) {
|
||||
Log.e(TAG, "onCalculateRouteFailure: " + i);
|
||||
Toast.makeText(this, "路径计算失败: " + i, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCameraInfo(AMapNaviCameraInfo[] aMapNaviCameraInfos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIntervalCameraInfo(AMapNaviCameraInfo aMapNaviCameraInfo, AMapNaviCameraInfo aMapNaviCameraInfo1, int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceAreaUpdate(AMapServiceAreaInfo[] aMapServiceAreaInfos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showCross(AMapNaviCross aMapNaviCross) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideCross() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showModeCross(AMapModelCross aMapModelCross) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideModeCross() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLaneInfo(AMapLaneInfo[] aMapLaneInfos, byte[] bytes, byte[] bytes1) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLaneInfo(AMapLaneInfo aMapLaneInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLaneInfo() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteSuccess(int[] ints) {
|
||||
Log.d(TAG, "onCalculateRouteSuccess");
|
||||
if (mAMapNavi != null) {
|
||||
mAMapNavi.startNavi(NaviType.GPS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyParallelRoad(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo[] aMapNaviTrafficFacilityInfos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnUpdateTrafficFacility(AMapNaviTrafficFacilityInfo aMapNaviTrafficFacilityInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAimlessModeStatistics(AimLessModeStat aimLessModeStat) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAimlessModeCongestionInfo(AimLessModeCongestionInfo aimLessModeCongestionInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayRing(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteSuccess(AMapCalcRouteResult aMapCalcRouteResult) {
|
||||
Log.d(TAG, "onCalculateRouteSuccess (result)");
|
||||
if (mAMapNavi != null) {
|
||||
mAMapNavi.startNavi(NaviType.GPS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalculateRouteFailure(AMapCalcRouteResult aMapCalcRouteResult) {
|
||||
Log.e(TAG, "onCalculateRouteFailure: " + aMapCalcRouteResult.getErrorCode());
|
||||
Toast.makeText(this, "路径计算失败", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviRouteNotify(AMapNaviRouteNotifyData aMapNaviRouteNotifyData) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGpsSignalWeak(boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReCalculateRouteForYaw() {
|
||||
Log.d(TAG, "onReCalculateRouteForYaw");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReCalculateRouteForTrafficJam() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArrivedWayPoint(int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGpsOpenStatus(boolean b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNaviInfoUpdate(NaviInfo naviInfo) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package com.lnkj.ln_jq_app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.amap.api.services.help.Inputtips;
|
||||
import com.amap.api.services.help.InputtipsQuery;
|
||||
import com.amap.api.services.help.Tip;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.activity.ComponentActivity;
|
||||
|
||||
/**
|
||||
* 搜索目的地页面
|
||||
*/
|
||||
public class SearchDestinationActivity extends ComponentActivity implements Inputtips.InputtipsListener {
|
||||
private static final String TAG = "SearchDestinationActivity";
|
||||
|
||||
public static final String EXTRA_RESULT_NAME = "result_name";
|
||||
public static final String EXTRA_RESULT_ADDRESS = "result_address";
|
||||
public static final String EXTRA_RESULT_LAT = "result_lat";
|
||||
public static final String EXTRA_RESULT_LON = "result_lon";
|
||||
public static final String EXTRA_RESULT_DISTRICT = "result_district";
|
||||
|
||||
// 静态回调接口
|
||||
public interface DestinationCallback {
|
||||
void onDestinationSelected(String name, String address, double lat, double lon, String district);
|
||||
}
|
||||
|
||||
private static DestinationCallback callback;
|
||||
|
||||
public static void setCallback(DestinationCallback callback) {
|
||||
SearchDestinationActivity.callback = callback;
|
||||
}
|
||||
|
||||
private EditText searchInput;
|
||||
private ListView suggestionList;
|
||||
private ImageView backBtn;
|
||||
|
||||
private ArrayAdapter<String> suggestionAdapter;
|
||||
private List<Tip> currentTipList;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
try {
|
||||
setContentView(R.layout.activity_search_destination);
|
||||
|
||||
initViews();
|
||||
setupListeners();
|
||||
|
||||
// 自动显示键盘
|
||||
showKeyboard();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in onCreate", e);
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
try {
|
||||
Log.d(TAG, "initViews started");
|
||||
searchInput = findViewById(R.id.search_input);
|
||||
suggestionList = findViewById(R.id.suggestion_list);
|
||||
backBtn = findViewById(R.id.back_btn);
|
||||
|
||||
currentTipList = new ArrayList<>();
|
||||
|
||||
Log.d(TAG, "initViews completed: searchInput=" + (searchInput != null ? "ok" : "null") + ", suggestionList=" + (suggestionList != null ? "ok" : "null") + ", backBtn=" + (backBtn != null ? "ok" : "null"));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in initViews", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
suggestionAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<>()) {
|
||||
@Override
|
||||
public View getView(int position, android.view.View convertView, android.view.ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
TextView text = view.findViewById(android.R.id.text1);
|
||||
text.setTextColor(Color.parseColor("#333333"));
|
||||
text.setTextSize(14);
|
||||
view.setBackgroundColor(Color.TRANSPARENT);
|
||||
return view;
|
||||
}
|
||||
};
|
||||
suggestionList.setAdapter(suggestionAdapter);
|
||||
suggestionList.setDivider(new ColorDrawable(Color.LTGRAY));
|
||||
suggestionList.setDividerHeight(1);
|
||||
}
|
||||
|
||||
|
||||
private void setupListeners() {
|
||||
try {
|
||||
Log.d(TAG, "setupListeners started");
|
||||
|
||||
// 返回按钮
|
||||
backBtn.setOnClickListener(v -> {
|
||||
Log.d(TAG, "Back button clicked");
|
||||
hideKeyboard();
|
||||
finish();
|
||||
});
|
||||
|
||||
// 文本变化监听
|
||||
searchInput.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
Log.d(TAG, "Text changed: " + (s != null ? s.toString() : "null") + ", length=" + (s != null ? s.length() : 0));
|
||||
if (s.length() > 1) {
|
||||
searchLocation(s.toString());
|
||||
} else {
|
||||
suggestionList.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
|
||||
// 列表点击
|
||||
suggestionList.setOnItemClickListener((parent, view, position, id) -> {
|
||||
Log.d(TAG, "Suggestion clicked at position: " + position);
|
||||
try {
|
||||
Tip tip = currentTipList.get(position);
|
||||
if (tip.getPoint() != null) {
|
||||
String name = tip.getName();
|
||||
String address = tip.getAddress();
|
||||
String district = tip.getDistrict();
|
||||
double lat = tip.getPoint().getLatitude();
|
||||
double lon = tip.getPoint().getLongitude();
|
||||
|
||||
Log.d(TAG, "Selected destination: " + name + ", lat=" + lat + ", lon=" + lon);
|
||||
|
||||
// 优先使用静态回调
|
||||
if (callback != null) {
|
||||
callback.onDestinationSelected(name, address, lat, lon, district);
|
||||
} else {
|
||||
// 降级使用 Intent 回调
|
||||
Intent result = new Intent();
|
||||
result.putExtra(EXTRA_RESULT_NAME, name);
|
||||
result.putExtra(EXTRA_RESULT_ADDRESS, address);
|
||||
result.putExtra(EXTRA_RESULT_LAT, lat);
|
||||
result.putExtra(EXTRA_RESULT_LON, lon);
|
||||
result.putExtra(EXTRA_RESULT_DISTRICT, district);
|
||||
setResult(RESULT_OK, result);
|
||||
}
|
||||
|
||||
hideKeyboard();
|
||||
finish();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error handling suggestion click", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
Log.d(TAG, "setupListeners completed");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in setupListeners", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void searchLocation(String keyword) {
|
||||
try {
|
||||
Log.d(TAG, "Searching for: " + keyword);
|
||||
InputtipsQuery query = new InputtipsQuery(keyword, "");
|
||||
query.setCityLimit(false);
|
||||
Inputtips inputtips = new Inputtips(this, query);
|
||||
inputtips.setInputtipsListener(this);
|
||||
inputtips.requestInputtipsAsyn();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in searchLocation", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onGetInputtips(List<Tip> tipList, int rCode) {
|
||||
Log.d(TAG, "onGetInputtips called, rCode=" + rCode + ", tipList size=" + (tipList != null ? tipList.size() : "null"));
|
||||
if (rCode == 1000 && tipList != null && !tipList.isEmpty()) {
|
||||
currentTipList.clear();
|
||||
currentTipList.addAll(tipList);
|
||||
|
||||
List<String> suggestionNames = new ArrayList<>();
|
||||
for (Tip tip : tipList) {
|
||||
String name = tip.getName();
|
||||
String district = tip.getDistrict();
|
||||
String displayText = district != null && !district.isEmpty() ? name + " " + district : name;
|
||||
suggestionNames.add(displayText);
|
||||
}
|
||||
|
||||
runOnUiThread(() -> {
|
||||
suggestionAdapter.clear();
|
||||
suggestionAdapter.addAll(suggestionNames);
|
||||
suggestionAdapter.notifyDataSetChanged();
|
||||
|
||||
if (suggestionNames.size() > 0) {
|
||||
suggestionList.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
suggestionList.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
runOnUiThread(() -> {
|
||||
suggestionList.setVisibility(View.GONE);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void showKeyboard() {
|
||||
searchInput.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.showSoftInput(searchInput, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
}
|
||||
|
||||
private void hideKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.hideSoftInputFromWindow(searchInput.getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onPointerCaptureChanged(boolean hasCapture) {
|
||||
super.onPointerCaptureChanged(hasCapture);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 清理回调,避免内存泄漏
|
||||
callback = null;
|
||||
}
|
||||
}
|
||||
BIN
ln_jq_app/android/app/src/main/res/drawable/back.png
Normal file
|
After Width: | Height: | Size: 705 B |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 背景颜色:使用浅灰色 -->
|
||||
<solid android:color="#ffffffff" />
|
||||
<!-- 圆角:8dp左右 -->
|
||||
<corners android:radius="8dp" />
|
||||
<!-- 边框:可选,如果需要更清晰的边缘可以加上 -->
|
||||
<stroke android:width="1dp" android:color="#EAEAEA" />
|
||||
</shape>
|
||||
BIN
ln_jq_app/android/app/src/main/res/drawable/car.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_close.png
Normal file
|
After Width: | Height: | Size: 880 B |
40
ln_jq_app/android/app/src/main/res/drawable/ic_fuel.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,14.667C10.946,14.667 13.333,12.496 13.333,9.818C13.333,7.192 11.556,4.364 8,1.333C4.444,4.364 2.667,7.192 2.667,9.818C2.667,12.496 5.054,14.667 8,14.667Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M6.286,6.333L8,8.123L9.714,6.333"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M6,8.719H10"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M6,10.509H10"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,8.719V12"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_location.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_marker.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
41
ln_jq_app/android/app/src/main/res/drawable/ic_mileage.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M3.667,1.375L2,13.375"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M12.333,1.375L13.988,13.36"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,1.375V3.375"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,11.042V13.375"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,6.042V8.375"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
13
ln_jq_app/android/app/src/main/res/drawable/ic_path.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h24v24h-24z"/>
|
||||
<path
|
||||
android:pathData="M6.518,10.241C4.181,10.241 2.29,12.135 2.29,14.469C2.29,16.804 6.518,21.684 6.518,21.684C6.518,21.684 10.746,16.806 10.746,14.469C10.746,12.133 8.853,10.241 6.518,10.241ZM6.518,15.616C5.853,15.616 5.313,15.077 5.313,14.411C5.313,13.745 5.853,13.206 6.518,13.206C7.184,13.206 7.723,13.745 7.723,14.411C7.723,15.077 7.181,15.616 6.518,15.616ZM19.676,2.301C18.567,2.301 17.67,3.198 17.67,4.307C17.67,5.416 19.676,7.729 19.676,7.729C19.676,7.729 21.682,5.416 21.682,4.307C21.682,3.198 20.785,2.301 19.676,2.301ZM19.676,4.851C19.36,4.851 19.104,4.595 19.104,4.279C19.104,3.963 19.36,3.707 19.676,3.707C19.992,3.707 20.248,3.963 20.248,4.279C20.246,4.595 19.99,4.851 19.676,4.851ZM18.026,13.185C17.386,12.813 16.629,12.674 15.898,12.538C14.845,12.344 14.196,12.191 14.074,11.72C14.051,11.627 14.037,11.493 14.156,11.291C14.325,11.005 14.855,10.422 16.519,9.735C16.993,9.539 17.449,9.382 17.801,9.27C18.168,9.153 18.393,8.768 18.296,8.395C18.199,8.019 17.812,7.792 17.442,7.909C17.056,8.03 16.543,8.205 15.999,8.43C14.431,9.074 13.404,9.798 12.945,10.579C12.663,11.057 12.584,11.575 12.713,12.077C12.877,12.709 13.303,13.185 13.978,13.485C14.482,13.71 15.073,13.818 15.642,13.923C16.256,14.036 16.889,14.153 17.318,14.402C17.597,14.563 17.888,14.819 17.916,15.487C17.958,16.427 16.88,17.448 14.881,18.367C13.833,18.848 12.764,19.192 12.099,19.385C11.729,19.493 11.5,19.869 11.589,20.244C11.68,20.625 12.064,20.86 12.441,20.752C13.144,20.55 14.295,20.183 15.45,19.654C16.547,19.152 17.424,18.606 18.054,18.032C18.938,17.228 19.364,16.349 19.322,15.426C19.278,14.413 18.842,13.661 18.026,13.185Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</group>
|
||||
</vector>
|
||||
20
ln_jq_app/android/app/src/main/res/drawable/ic_person.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,6.042C9.289,6.042 10.333,4.997 10.333,3.708C10.333,2.42 9.289,1.375 8,1.375C6.711,1.375 5.667,2.42 5.667,3.708C5.667,4.997 6.711,6.042 8,6.042Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M2,12.975V13.375H14V12.975C14,11.481 14,10.735 13.709,10.164C13.454,9.663 13.046,9.255 12.544,8.999C11.974,8.708 11.227,8.708 9.733,8.708H6.267C4.773,8.708 4.026,8.708 3.456,8.999C2.954,9.255 2.546,9.663 2.291,10.164C2,10.735 2,11.481 2,12.975Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
12
ln_jq_app/android/app/src/main/res/drawable/ic_phone.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M5.665,1.937C5.908,1.937 6.131,2.068 6.248,2.28L7.064,3.749C7.171,3.941 7.176,4.174 7.077,4.371L6.292,5.942C6.292,5.942 6.519,7.112 7.472,8.065C8.425,9.018 9.591,9.242 9.591,9.242L11.162,8.456C11.359,8.358 11.592,8.363 11.785,8.47L13.258,9.289C13.469,9.407 13.6,9.63 13.6,9.872V11.563C13.6,12.424 12.8,13.046 11.984,12.771C10.308,12.205 7.707,11.128 6.058,9.479C4.409,7.831 3.332,5.229 2.767,3.553C2.491,2.737 3.113,1.937 3.975,1.937H5.665Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"/>
|
||||
</vector>
|
||||
40
ln_jq_app/android/app/src/main/res/drawable/ic_price.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,14.042C11.682,14.042 14.667,11.057 14.667,7.375C14.667,3.693 11.682,0.708 8,0.708C4.318,0.708 1.333,3.693 1.333,7.375C1.333,11.057 4.318,14.042 8,14.042Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M6,6.708H10"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M6,8.708H10"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8.003,6.708V10.708"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M10,4.375L8,6.375L6,4.375"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
26
ln_jq_app/android/app/src/main/res/drawable/ic_search.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="18"
|
||||
android:viewportHeight="18">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M7.875,14.5C11.396,14.5 14.25,11.646 14.25,8.125C14.25,4.604 11.396,1.75 7.875,1.75C4.354,1.75 1.5,4.604 1.5,8.125C1.5,11.646 4.354,14.5 7.875,14.5Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#4E5969"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M9.997,5.629C9.454,5.086 8.704,4.75 7.875,4.75C7.047,4.75 6.297,5.086 5.754,5.629"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#4E5969"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M12.458,12.707L15.64,15.889"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#4E5969"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
19
ln_jq_app/android/app/src/main/res/drawable/ic_time.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8,14.042C11.682,14.042 14.667,11.057 14.667,7.375C14.667,3.693 11.682,0.708 8,0.708C4.318,0.708 1.333,3.693 1.333,7.375C1.333,11.057 4.318,14.042 8,14.042Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8.003,3.375L8.003,7.378L10.829,10.204"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
37
ln_jq_app/android/app/src/main/res/drawable/ic_toll.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M1.333,2.042H3.667C3.667,2.042 4,3.708 5.667,3.708C7.333,3.708 7.667,2.042 7.667,2.042H14.667V12.708H7.667C7.667,12.708 7.333,11.042 5.667,11.042C4,11.042 3.667,12.708 3.667,12.708H1.333V2.042Z"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M5.667,5.708V6.375"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M5.667,8.375V9.042"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8.333,6.375H12"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M8.333,8.375H12"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#017137"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
BIN
ln_jq_app/android/app/src/main/res/drawable/ic_un_marker.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#FFFFFF" />
|
||||
<corners
|
||||
android:topLeftRadius="24dp"
|
||||
android:topRightRadius="24dp"
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:bottomRightRadius="0dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#FFFFFF"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/back_btn"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:src="@drawable/back" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:text="选择地点"
|
||||
android:textColor="#000000"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:layout_margin="12dp"
|
||||
android:background="@drawable/bg_search_input"
|
||||
android:hint="输入地址"
|
||||
android:imeOptions="actionDone"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:textColor="#333333"
|
||||
android:textColorHint="#999999"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/suggestion_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
Before Width: | Height: | Size: 6.0 KiB |
BIN
ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
@@ -21,11 +21,9 @@
|
||||
.amap-callamap,
|
||||
.amap-lib-driving-callBtn,
|
||||
.amap-copyright,
|
||||
.amap-logo {
|
||||
display: none !important;
|
||||
}
|
||||
.amap-logo{bottom: 60px}
|
||||
|
||||
/* 去除高德默认的 label 边框和背景 */
|
||||
/* 去除高德默认的 label 边框 and 背景 */
|
||||
.amap-marker-label {
|
||||
border: none !important;
|
||||
background-color: transparent !important;
|
||||
@@ -109,7 +107,7 @@
|
||||
/* --- 导航结果面板 (底部弹出) --- */
|
||||
#panel {
|
||||
position: fixed;
|
||||
bottom: 75px;
|
||||
bottom: 95px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 35%;
|
||||
@@ -129,7 +127,7 @@
|
||||
#location-btn {
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
bottom: 75px;
|
||||
bottom: 105px;
|
||||
/* 默认位置 */
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
@@ -159,7 +157,7 @@
|
||||
/* --- 调整比例尺位置 --- */
|
||||
.amap-scalecontrol {
|
||||
/* 初始状态:避开底部的定位按钮或留出安全间距 */
|
||||
bottom: 80px !important;
|
||||
bottom: 110px !important;
|
||||
left: 10px !important;
|
||||
transition: bottom 0.3s ease;
|
||||
/* 增加平滑动画 */
|
||||
@@ -195,10 +193,10 @@
|
||||
|
||||
<div id="search-box">
|
||||
<div class="input-row">
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" />
|
||||
<input id="startInput" placeholder="起点: 请输入当前地点" onfocus="this.select()" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" />
|
||||
<input id="endInput" placeholder="终点: 请输入目的地" onfocus="this.select()" />
|
||||
<button onclick="startRouteSearch()">路径规划</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,6 +219,7 @@
|
||||
var currentLat, currentLng;
|
||||
var isTruckMode = false;
|
||||
var isInitialLocationSet = false;
|
||||
var stationMarkers = []; // 存储所有站点的标记
|
||||
|
||||
|
||||
function initMap() {
|
||||
@@ -243,6 +242,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 点击地图空白处重置状态
|
||||
map.on('click', function() {
|
||||
resetSearchState();
|
||||
});
|
||||
|
||||
// 添加基础控件
|
||||
map.addControl(new AMap.Scale());
|
||||
map.addControl(new AMap.ToolBar({
|
||||
@@ -284,6 +288,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置搜索状态,隐藏面板和路线
|
||||
*/
|
||||
function resetSearchState() {
|
||||
if (document.body.classList.contains('panel-active')) {
|
||||
console.log("JS->: 重置地图状态");
|
||||
document.body.classList.remove('panel-active');
|
||||
var panel = document.getElementById('panel');
|
||||
panel.style.display = 'none';
|
||||
if (driving) driving.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心功能 1: 接收 Flutter 传来的定位数据
|
||||
* Flutter 端调用: webViewController.evaluateJavascript("updateMyLocation(...)")
|
||||
@@ -336,6 +353,8 @@
|
||||
fetchStationInfo(addressComponent.province, addressComponent.city,
|
||||
addressComponent.district, lat, lng);
|
||||
|
||||
fetchStationInfoList(lat, lng);
|
||||
|
||||
// 策略1: 优先使用最近的、类型合适的POI的名称
|
||||
if (pois && pois.length > 0) {
|
||||
// 查找第一个类型不是“商务住宅”或“地名地址信息”的POI,这类POI通常是具体的建筑或地点名
|
||||
@@ -397,7 +416,6 @@
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// "asoco-token": "e28eada8-4611-4dc2-a942-0122e52f52da"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
province: province,
|
||||
@@ -437,6 +455,79 @@
|
||||
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取站点列表
|
||||
*/
|
||||
function fetchStationInfoList(lat, lng) {
|
||||
fetch('https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
longitude: lng,
|
||||
latitude: lat,
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('网络响应错误: ' + response.status);
|
||||
}
|
||||
return response.json(); // 解析 JSON
|
||||
})
|
||||
.then(res => {
|
||||
console.log("JS->:2 接口完整返回:", JSON.stringify(res));
|
||||
if (res.code === 0 && res.data && Array.isArray(res.data)) {
|
||||
// 1. 清除旧的站点标记
|
||||
stationMarkers.forEach(m => m.setMap(null));
|
||||
stationMarkers = [];
|
||||
|
||||
// 2. 循环标记所有加氢站
|
||||
res.data.forEach(station => {
|
||||
var stationIcon = new AMap.Icon({
|
||||
size: new AMap.Size(32, 32),
|
||||
image: 'ic_tag.png',
|
||||
imageSize: new AMap.Size(32, 32)
|
||||
});
|
||||
|
||||
var sMarker = new AMap.Marker({
|
||||
map: map,
|
||||
position: [station.longitude, station.latitude],
|
||||
icon: stationIcon,
|
||||
offset: new AMap.Pixel(-16, -32),
|
||||
title: station.name,
|
||||
label: {
|
||||
content: '<div class="custom-bubble">' + station.name +
|
||||
'</div>',
|
||||
direction: 'top'
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 绑定点击事件:选中即为目的地,并开始规划
|
||||
sMarker.on('click', function () {
|
||||
var stationName = station.name || "目的地";
|
||||
document.getElementById('endInput').value = station.address ||
|
||||
stationName;
|
||||
|
||||
// 更新当前的 destMarker
|
||||
if (destMarker && destMarker !== sMarker) destMarker.setMap(null);
|
||||
destMarker = sMarker;
|
||||
|
||||
// 直接传入坐标对象,避免关键字搜索失败
|
||||
var loc = new AMap.LngLat(station.longitude, station.latitude);
|
||||
startRouteSearch(loc);
|
||||
});
|
||||
|
||||
stationMarkers.push(sMarker);
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("JS->: 业务报错或无数据:", res.message);
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('JS->:获取站点信息失败:', err));
|
||||
}
|
||||
|
||||
/**
|
||||
* 地理编码并在地图标记终点
|
||||
*/
|
||||
@@ -447,7 +538,6 @@
|
||||
if (destMarker) destMarker.setMap(null);
|
||||
|
||||
// 2. 创建自定义图标
|
||||
// 假设图标大小为 32x32,你可以根据实际图片尺寸调整 Size
|
||||
var destIcon = new AMap.Icon({
|
||||
size: new AMap.Size(32, 32), // 图标尺寸
|
||||
image: 'ic_tag.png', // 本地图片路径
|
||||
@@ -459,8 +549,6 @@
|
||||
map: map,
|
||||
position: [longitude, latitude],
|
||||
icon: destIcon, // 使用自定义图标
|
||||
// 偏移量:如果图标底部中心是尖角,offset 设为宽的一半的负数,高度的负数
|
||||
// 这样能确保图片的底部尖端指向地图上的精确位置
|
||||
offset: new AMap.Pixel(-16, -32),
|
||||
title: name,
|
||||
label: {
|
||||
@@ -469,17 +557,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 打印调试信息
|
||||
console.log("JS->: 终点标记已添加", address, loc.toString());
|
||||
|
||||
// 5. 自动调整视野包含起点和终点
|
||||
// if (marker) {
|
||||
// // 如果起点标志已存在,缩放地图以展示两者
|
||||
// map.setFitView([marker, destMarker], false, [60, 60, 60, 60]);
|
||||
// } else {
|
||||
// // 如果没有起点,直接跳到终点
|
||||
// map.setCenter(loc);
|
||||
// }
|
||||
console.log("JS->: 终点标记已添加", address);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,11 +576,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 路径规划
|
||||
* @param {AMap.LngLat} [destLoc] 可选的终点坐标
|
||||
*/
|
||||
function startRouteSearch() {
|
||||
// 获取输入框的文字
|
||||
function startRouteSearch(destLoc) {
|
||||
var startKw = document.getElementById('startInput').value;
|
||||
var endKw = document.getElementById('endInput').value;
|
||||
|
||||
@@ -510,63 +589,59 @@
|
||||
alert("请输入起点");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!endKw) {
|
||||
alert("请输入终点");
|
||||
return;
|
||||
}
|
||||
|
||||
// 清除旧路线
|
||||
if (driving) driving.clear();
|
||||
|
||||
// 收起键盘
|
||||
document.getElementById('startInput').blur();
|
||||
document.getElementById('endInput').blur();
|
||||
|
||||
// --- 构造路径规划的点 (使用数组方式,更灵活) ---
|
||||
var points = [];
|
||||
|
||||
// 1. 处理起点逻辑
|
||||
// 如果输入框是空的,或者写着 "我的位置",则使用 GPS 坐标
|
||||
if (!startKw || startKw === '我的位置') {
|
||||
// 1. 起点逻辑
|
||||
if (!startKw || startKw === '我的位置' || startKw.includes('当前位置')) {
|
||||
if (!currentLng || !currentLat) {
|
||||
// 如果还没获取到定位
|
||||
if (window.flutter_inappwebview) {
|
||||
window.flutter_inappwebview.callHandler('requestLocation');
|
||||
}
|
||||
if (window.flutter_inappwebview) window.flutter_inappwebview.callHandler('requestLocation');
|
||||
alert("正在获取定位,请稍后...");
|
||||
return;
|
||||
}
|
||||
// 使用精准坐标对象 (避免高德去猜 '我的位置' 关键词)
|
||||
points.push({
|
||||
keyword: '我的位置', // 用于显示的名字
|
||||
location: new AMap.LngLat(currentLng, currentLat) // 实际导航用的坐标
|
||||
keyword: '我的位置',
|
||||
location: new AMap.LngLat(currentLng, currentLat)
|
||||
});
|
||||
} else {
|
||||
// 如果用户手动输入了地点 (例如 "北京南站")
|
||||
// 直接存入关键词,让高德自己去搜
|
||||
points.push({
|
||||
keyword: startKw
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 处理终点逻辑 (通常是关键词)
|
||||
// 2. 终点逻辑:如果有传入坐标,则直接使用坐标导航,成功率最高
|
||||
if (destLoc) {
|
||||
points.push({
|
||||
keyword: endKw,
|
||||
location: destLoc // 关键:使用精确坐标
|
||||
});
|
||||
} else {
|
||||
points.push({
|
||||
keyword: endKw
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 发起搜索
|
||||
// points 数组里现在是一个起点对象和一个终点对象
|
||||
driving.search(points, function (status, result) {
|
||||
if (status === 'complete') {
|
||||
console.log('JS: 规划成功');
|
||||
var panel = document.getElementById('panel');
|
||||
panel.style.display = 'block';
|
||||
document.body.classList.add('panel-active');
|
||||
} else {
|
||||
console.log('JS: 规划失败', result);
|
||||
alert("规划失败,请检查起终点名称");
|
||||
}
|
||||
// else {
|
||||
// console.error('JS: 规划失败', result);
|
||||
// // 如果坐标规划都失败了,通常是由于起终点距离过近或政策限制(如货车禁行)
|
||||
// alert("路径规划未成功,请尝试微调起终点");
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
BIN
ln_jq_app/assets/images/history_bg.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
ln_jq_app/assets/images/ic_attention@2x.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
ln_jq_app/assets/images/ic_close@2x.png
Normal file
|
After Width: | Height: | Size: 892 B |
BIN
ln_jq_app/assets/images/ic_ex_menu@2x.png
Normal file
|
After Width: | Height: | Size: 394 B |
|
Before Width: | Height: | Size: 573 B After Width: | Height: | Size: 947 B |
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 594 B |
BIN
ln_jq_app/assets/images/ic_serch@2x.png
Normal file
|
After Width: | Height: | Size: 636 B |
BIN
ln_jq_app/assets/images/ic_upload@2x.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
ln_jq_app/assets/images/mall_bar@2x.png
Normal file
|
After Width: | Height: | Size: 896 B |
BIN
ln_jq_app/assets/images/mall_pay_success@2x.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
ln_jq_app/assets/images/rule_bg@2x.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
ln_jq_app/assets/images/rule_bg_1@2x.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
ln_jq_app/assets/images/tips_1@2x.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
ln_jq_app/assets/images/tips_2@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ln_jq_app/assets/images/tips_3@2x.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
ln_jq_app/assets/images/tips_4@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ln_jq_app/assets/images/tips_5@2x.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
51
ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK.podspec
Normal file
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# Be sure to run `pod lib lint AMapNavIOSSDK.podspec' to ensure this is a
|
||||
# valid spec before submitting.
|
||||
#
|
||||
# Any lines starting with a # are optional, but their use is encouraged
|
||||
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
|
||||
#
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'AMapNavIOSSDK'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'A short description of AMapNavIOSSDK.'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
# * Try to keep it short, snappy and to the point.
|
||||
# * Write the description between the DESC delimiters below.
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
|
||||
s.description = <<-DESC
|
||||
TODO: Add long description of the pod here.
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://github.com/xiaoshuai/AMapNavIOSSDK'
|
||||
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'xiaoshuai' => 'xiaoshuai@net.cn' }
|
||||
s.source = { :git => 'https://github.com/xiaoshuai/AMapNavIOSSDK.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
|
||||
s.ios.deployment_target = '12.0'
|
||||
|
||||
s.source_files = 'AMapNavIOSSDK/Classes/**/*'
|
||||
s.resource = 'AMapNavIOSSDK/**/*.bundle'
|
||||
s.resource_bundles = {
|
||||
'AMapNavIOSSDKPrivacyInfo' => ['AMapNavIOSSDK/**/PrivacyInfo.xcprivacy']
|
||||
}
|
||||
|
||||
# s.public_header_files = 'Pod/Classes/**/*.h'
|
||||
# s.frameworks = 'UIKit', 'MapKit'
|
||||
# s.dependency 'AFNetworking', '~> 2.3'
|
||||
|
||||
s.dependency 'Masonry'
|
||||
s.dependency 'MJExtension'
|
||||
|
||||
s.dependency 'AMapNavi-NO-IDFA'
|
||||
s.dependency 'AMapLocation-NO-IDFA'
|
||||
s.dependency 'AMapSearch-NO-IDFA'
|
||||
s.dependency 'MBProgressHUD'
|
||||
|
||||
end
|
||||
|
After Width: | Height: | Size: 966 B |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 844 B |
|
After Width: | Height: | Size: 792 B |
|
After Width: | Height: | Size: 909 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 864 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 840 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 829 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>0A2A.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// AAddHPopView.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
* 气泡弹框视图
|
||||
*
|
||||
* 用法:
|
||||
* AAddHPopView *pop = [[AAddHPopView alloc] init];
|
||||
* [pop showInView:self.view sourceView:addHbtn];
|
||||
*
|
||||
* - 点击气泡外部自动消失
|
||||
* - 箭头在右下角45度,指向 sourceView 中心
|
||||
*/
|
||||
@interface AAddHPopView : UIView
|
||||
|
||||
/// 展示气泡,sourceView 为箭头指向的锚定按钮(坐标系属于 view)
|
||||
- (void)showInView:(UIView *)view sourceView:(UIView *)sourceView;
|
||||
|
||||
/// 手动消失(点击空白处会自动调用)
|
||||
- (void)dismiss;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,359 @@
|
||||
//
|
||||
// AAddHPopView.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
|
||||
#import "AAddHPopView.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "UIColor+ANavMap.h"
|
||||
|
||||
// ─── 尺寸常量 ────────────────────────────────────────────────────────────────
|
||||
static const CGFloat kBubbleWidth = 200.f; // 气泡宽度
|
||||
static const CGFloat kHeaderHeight = 50.f; // 顶部绿色标题区高度
|
||||
static const CGFloat kOptionHeight = 60.f; // 每行选项高度
|
||||
static const CGFloat kArrowWidth = 50.f; // 箭头底边宽(aw = ah*2 使左右45度对称)
|
||||
static const CGFloat kArrowHeight = 15.f; // 箭头高度
|
||||
static const CGFloat kBubbleRadius = 15.f; // 气泡圆角半径
|
||||
static const CGFloat kBubbleGap = 5.f; // 气泡底部与 sourceView 顶部间距
|
||||
static const CGFloat kScreenPadding = 15.f; // 气泡距屏幕边缘最小距离
|
||||
|
||||
|
||||
// ─── 自定义气泡容器:自绘白色气泡 + 底部箭头 ─────────────────────────────────
|
||||
@interface _ABubbleContainerView : UIView
|
||||
/// 箭头中心 X(相对于本 view 坐标系),用于 showInView 精确定位
|
||||
@property (nonatomic, assign) CGFloat arrowCenterX;
|
||||
@end
|
||||
|
||||
@implementation _ABubbleContainerView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
_arrowCenterX = kBubbleWidth / 2.f;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setArrowCenterX:(CGFloat)arrowCenterX {
|
||||
_arrowCenterX = arrowCenterX;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGFloat bw = kBubbleWidth; // 160
|
||||
CGFloat bh = kHeaderHeight + kOptionHeight * 2.f; // 150
|
||||
CGFloat r = kBubbleRadius; // 10
|
||||
CGFloat aw = kArrowWidth; // 16 箭头底边宽
|
||||
CGFloat ah = kArrowHeight; // 10 箭头高度
|
||||
|
||||
// 箭头在底部偏右:指向右下45度
|
||||
// 箭头底边: 从 (arrowX-aw/2, bh) 到 (arrowX, bh)
|
||||
// 箭头尖端: (arrowX + ah, bh + ah) — 相对于底边中心向右下45度
|
||||
CGFloat arrowX = bw - 15.f; // 箭头底边右端 X(偏右下)
|
||||
CGFloat arrowTipX = arrowX + ah; // 箭头尖端 X(向右偏移ah = 45度)
|
||||
CGFloat arrowTipY = bh + ah; // 箭头尖端 Y(向下偏移ah = 45度)
|
||||
|
||||
UIBezierPath *path = [UIBezierPath bezierPath];
|
||||
|
||||
// ── 顶部左圆角 → 顶部右圆角 ──
|
||||
[path moveToPoint:CGPointMake(r, 0)];
|
||||
[path addLineToPoint:CGPointMake(bw - r, 0)];
|
||||
[path addArcWithCenter:CGPointMake(bw - r, r) radius:r
|
||||
startAngle:-M_PI_2 endAngle:0 clockwise:YES];
|
||||
|
||||
// ── 右边 → 右下圆角 ──
|
||||
[path addLineToPoint:CGPointMake(bw, bh - r)];
|
||||
[path addArcWithCenter:CGPointMake(bw - r, bh - r) radius:r
|
||||
startAngle:0 endAngle:M_PI_2 clockwise:YES];
|
||||
|
||||
// ── 底边 → 箭头底边右端 ──
|
||||
[path addLineToPoint:CGPointMake(arrowX, bh)];
|
||||
// 箭头斜边(45度向右下到尖端)
|
||||
[path addLineToPoint:CGPointMake(arrowTipX, arrowTipY)];
|
||||
// 箭头底边左端(垂直向上)
|
||||
[path addLineToPoint:CGPointMake(arrowX - ah, bh)];
|
||||
|
||||
// ── 左下圆角 ──
|
||||
[path addArcWithCenter:CGPointMake(r, bh - r) radius:r
|
||||
startAngle:M_PI_2 endAngle:M_PI clockwise:YES];
|
||||
|
||||
// ── 左边 → 左上圆角 ──
|
||||
[path addLineToPoint:CGPointMake(0, r)];
|
||||
[path addArcWithCenter:CGPointMake(r, r) radius:r
|
||||
startAngle:M_PI endAngle:-M_PI_2 clockwise:YES];
|
||||
[path closePath];
|
||||
|
||||
// ── 阴影 + 白色填充 ──
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
CGContextSaveGState(ctx);
|
||||
CGContextSetShadowWithColor(ctx, CGSizeMake(0, 3), 10.f,
|
||||
[[UIColor blackColor] colorWithAlphaComponent:0.15f].CGColor);
|
||||
[[UIColor whiteColor] setFill];
|
||||
[path fill];
|
||||
CGContextRestoreGState(ctx);
|
||||
[[UIColor whiteColor] setFill];
|
||||
[path fill];
|
||||
|
||||
// 保存箭头尖端坐标用于定位
|
||||
_arrowTipX = arrowTipX;
|
||||
_arrowTipY = arrowTipY;
|
||||
}
|
||||
|
||||
// 供外部访问的箭头尖端坐标
|
||||
static CGFloat _arrowTipX = 0;
|
||||
static CGFloat _arrowTipY = 0;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// ─── AAddHPopView ────────────────────────────────────────────────────────────
|
||||
|
||||
@interface AAddHPopView ()
|
||||
|
||||
@property (nonatomic, strong) _ABubbleContainerView *bubbleContainer;
|
||||
@property (nonatomic, strong) UIView *headerView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
@property (nonatomic, strong) UIView *separatorLine1;
|
||||
@property (nonatomic, strong) UILabel *option1Label;
|
||||
@property (nonatomic, strong) UIView *separatorLine2;
|
||||
@property (nonatomic, strong) UILabel *option2Label;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AAddHPopView
|
||||
|
||||
#pragma mark - Init
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithFrame:CGRectZero];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self p_setupUI];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Setup UI
|
||||
|
||||
- (void)p_setupUI {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
// ── 点击空白处(self)消失 ──
|
||||
UITapGestureRecognizer *bgTap = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(dismiss)];
|
||||
bgTap.cancelsTouchesInView = NO;
|
||||
[self addGestureRecognizer:bgTap];
|
||||
|
||||
// ── 气泡容器 ──
|
||||
[self addSubview:self.bubbleContainer];
|
||||
|
||||
// ── header(绿色标题区,仅裁剪上两角) ──
|
||||
[self.bubbleContainer addSubview:self.headerView];
|
||||
[self.headerView addSubview:self.titleLabel];
|
||||
|
||||
// ── 分隔线 1 ──
|
||||
[self.bubbleContainer addSubview:self.separatorLine1];
|
||||
|
||||
// ── 选项 1 ──
|
||||
[self.bubbleContainer addSubview:self.option1Label];
|
||||
|
||||
// ── 分隔线 2 ──
|
||||
[self.bubbleContainer addSubview:self.separatorLine2];
|
||||
|
||||
// ── 选项 2 ──
|
||||
[self.bubbleContainer addSubview:self.option2Label];
|
||||
|
||||
// ── 阻止气泡内点击冒泡到 bgTap ──
|
||||
UITapGestureRecognizer *bubbleTap = [[UITapGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(p_bubbleTapped)];
|
||||
[self.bubbleContainer addGestureRecognizer:bubbleTap];
|
||||
|
||||
// ── Masonry 内部约束(基于 bubbleContainer) ──
|
||||
// 子视图宽度统一约束到 kBubbleWidth(160),气泡主体实际宽度
|
||||
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.left.equalTo(self.bubbleContainer);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(kHeaderHeight);
|
||||
}];
|
||||
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.headerView);
|
||||
}];
|
||||
|
||||
[self.separatorLine1 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.headerView.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
[self.option1Label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.separatorLine1.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(kOptionHeight);
|
||||
}];
|
||||
|
||||
[self.separatorLine2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.option1Label.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
[self.option2Label mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.separatorLine2.mas_bottom);
|
||||
make.left.mas_equalTo(@0);
|
||||
make.width.mas_equalTo(kBubbleWidth);
|
||||
make.height.mas_equalTo(kOptionHeight);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Show / Dismiss
|
||||
|
||||
- (void)showInView:(UIView *)view sourceView:(UIView *)sourceView {
|
||||
self.frame = view.bounds;
|
||||
[view addSubview:self];
|
||||
|
||||
// ── 计算 sourceView 在 view 坐标系中的 frame ──
|
||||
CGRect srcFrame = [sourceView convertRect:sourceView.bounds toView:view];
|
||||
|
||||
// ── 容器尺寸 ──
|
||||
CGFloat containerW = kBubbleWidth; // 160
|
||||
CGFloat containerH = kHeaderHeight + kOptionHeight * 2.f; // 150
|
||||
CGFloat ah = kArrowHeight; // 16 箭头高度
|
||||
|
||||
// ── 箭头尖端坐标(相对于 bubbleContainer 左下角为原点)──
|
||||
// 与 drawRect 中一致:arrowX = bw - 35, arrowTipX = arrowX + ah
|
||||
CGFloat arrowX_inContainer = containerW - 25.f; // 箭头底边右端 X
|
||||
CGFloat arrowTipX_inContainer = arrowX_inContainer + ah; // 箭头尖端 X(右下45度)
|
||||
CGFloat arrowTipY_inContainer = containerH + ah; // 箭头尖端 Y
|
||||
|
||||
// ── 位置计算:气泡在按钮左上方,箭头尖端指向按钮中心 ──
|
||||
CGFloat srcCenterX = CGRectGetMidX(srcFrame);
|
||||
CGFloat srcCenterY = CGRectGetMidY(srcFrame);
|
||||
|
||||
// 箭头尖端应指向按钮中心
|
||||
CGFloat containerX = srcCenterX - arrowTipX_inContainer;
|
||||
containerX = MAX(kScreenPadding, MIN(containerX, view.bounds.size.width - containerW - kScreenPadding));
|
||||
|
||||
// 气泡顶部位置
|
||||
CGFloat containerY = srcCenterY - arrowTipY_inContainer;
|
||||
containerY = MAX(kScreenPadding, containerY);
|
||||
|
||||
self.bubbleContainer.bounds = CGRectMake(0, 0, containerW, containerH + ah);
|
||||
|
||||
// 锚点设置在箭头尖端位置(相对于容器),实现箭头精准指向
|
||||
self.bubbleContainer.layer.anchorPoint = CGPointMake(arrowTipX_inContainer / containerW,
|
||||
arrowTipY_inContainer / (containerH + ah));
|
||||
self.bubbleContainer.center = CGPointMake(srcCenterX - 25, srcCenterY - 20);
|
||||
|
||||
// ── 入场动画 ──
|
||||
self.alpha = 0.f;
|
||||
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
|
||||
|
||||
[UIView animateWithDuration:0.25f
|
||||
delay:0.f
|
||||
usingSpringWithDamping:0.72f
|
||||
initialSpringVelocity:0.3f
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
self.alpha = 1.f;
|
||||
self.bubbleContainer.transform = CGAffineTransformIdentity;
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)dismiss {
|
||||
[UIView animateWithDuration:0.18f
|
||||
delay:0.f
|
||||
options:UIViewAnimationOptionCurveEaseIn
|
||||
animations:^{
|
||||
self.alpha = 0.f;
|
||||
self.bubbleContainer.transform = CGAffineTransformMakeScale(0.85f, 0.85f);
|
||||
} completion:^(BOOL finished) {
|
||||
[self removeFromSuperview];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Private Actions
|
||||
|
||||
- (void)p_bubbleTapped {
|
||||
// 阻止冒泡,气泡内点击不消失
|
||||
}
|
||||
|
||||
#pragma mark - Lazy Load
|
||||
|
||||
- (_ABubbleContainerView *)bubbleContainer {
|
||||
if (!_bubbleContainer) {
|
||||
_bubbleContainer = [[_ABubbleContainerView alloc] init];
|
||||
}
|
||||
return _bubbleContainer;
|
||||
}
|
||||
|
||||
- (UIView *)headerView {
|
||||
if (!_headerView) {
|
||||
_headerView = [[UIView alloc] init];
|
||||
_headerView.backgroundColor = [UIColor hp_colorWithRGBHex:0x1BA855];
|
||||
if (@available(iOS 11.0, *)) {
|
||||
_headerView.layer.cornerRadius = kBubbleRadius;
|
||||
_headerView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
|
||||
_headerView.clipsToBounds = YES;
|
||||
}
|
||||
}
|
||||
return _headerView;
|
||||
}
|
||||
|
||||
- (UILabel *)titleLabel {
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.text = @"加氢规划模式";
|
||||
_titleLabel.textColor = [UIColor whiteColor];
|
||||
_titleLabel.font = [UIFont boldSystemFontOfSize:15.f];
|
||||
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UIView *)separatorLine1 {
|
||||
if (!_separatorLine1) {
|
||||
_separatorLine1 = [[UIView alloc] init];
|
||||
_separatorLine1.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
|
||||
}
|
||||
return _separatorLine1;
|
||||
}
|
||||
|
||||
- (UILabel *)option1Label {
|
||||
if (!_option1Label) {
|
||||
_option1Label = [[UILabel alloc] init];
|
||||
_option1Label.text = @"送货规划模式";
|
||||
_option1Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
|
||||
_option1Label.font = [UIFont boldSystemFontOfSize:14.f];
|
||||
_option1Label.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _option1Label;
|
||||
}
|
||||
|
||||
- (UIView *)separatorLine2 {
|
||||
if (!_separatorLine2) {
|
||||
_separatorLine2 = [[UIView alloc] init];
|
||||
_separatorLine2.backgroundColor = [UIColor colorWithWhite:0.88f alpha:1.f];
|
||||
}
|
||||
return _separatorLine2;
|
||||
}
|
||||
|
||||
- (UILabel *)option2Label {
|
||||
if (!_option2Label) {
|
||||
_option2Label = [[UILabel alloc] init];
|
||||
_option2Label.text = @"成本计算模式";
|
||||
_option2Label.textColor = [UIColor hp_colorWithRGBHex:0xC9CDD4];
|
||||
_option2Label.font = [UIFont boldSystemFontOfSize:14.f];
|
||||
_option2Label.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _option2Label;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// ABaseViewController.h
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/5.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Masonry/Masonry.h>
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
#define kRoutePlanBarHeight (self.navigationController.navigationBar.frame.size.height + UIApplication.sharedApplication.statusBarFrame.size.height + 0)
|
||||
|
||||
#define kRoutePlanStatusBarHeight (UIApplication.sharedApplication.statusBarFrame.size.height + 0)
|
||||
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ABaseViewController : UIViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// ABaseViewController.m
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/5.
|
||||
//
|
||||
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
@interface ABaseViewController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ABaseViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
/*
|
||||
#pragma mark - Navigation
|
||||
|
||||
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
||||
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
||||
// Get the new view controller using [segue destinationViewController].
|
||||
// Pass the selected object to the new view controller.
|
||||
}
|
||||
*/
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// ACustomAnnotationView.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/17.
|
||||
//
|
||||
|
||||
#import <AMapNaviKit/AMapNaviKit.h>
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ACustomAnnotationView : MAAnnotationView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// ACustomAnnotationView.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/17.
|
||||
//
|
||||
|
||||
#import "ACustomAnnotationView.h"
|
||||
|
||||
// ─── 布局常量 ─────────────────────────────────────────────────────────────────
|
||||
static const CGFloat kIconSize = 30; // 图标宽高
|
||||
static const CGFloat kIconTextGap = 6.0; // 图标与文字间距
|
||||
static const CGFloat kMaxTextWidth = 100.0; // 文字区域最大宽度
|
||||
static const CGFloat kMaxTextLines = 2; // 最多行数
|
||||
static const CGFloat kVPad = 4.0; // 整体上下内边距(用于 centerOffset 微调)
|
||||
|
||||
@interface ACustomAnnotationView ()
|
||||
|
||||
@property (nonatomic, strong) UIImageView *iconView;
|
||||
@property (nonatomic, strong) UILabel *titleLabel;
|
||||
|
||||
/// 当前是否选中(用于更新图标 & 文字样式)
|
||||
@property (nonatomic, assign) BOOL isAnnotationSelected;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ACustomAnnotationView
|
||||
|
||||
#pragma mark - Init / Reuse
|
||||
|
||||
- (instancetype)initWithAnnotation:(id<MAAnnotation>)annotation
|
||||
reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
// 关闭 MAAnnotationView 自带的 image 渲染,避免干扰
|
||||
self.image = nil;
|
||||
[self _buildSubviews];
|
||||
[self _updateFromAnnotation];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setAnnotation:(id<MAAnnotation>)annotation {
|
||||
[super setAnnotation:annotation];
|
||||
[self _updateFromAnnotation];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark - Build
|
||||
|
||||
- (void)_buildSubviews {
|
||||
// ── 图标 ──────────────────────────────────────────────────
|
||||
if (!self.iconView) {
|
||||
UIImageView *iv = [[UIImageView alloc] init];
|
||||
iv.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[self addSubview:iv];
|
||||
self.iconView = iv;
|
||||
}
|
||||
|
||||
// ── 文字 ──────────────────────────────────────────────────
|
||||
if (!self.titleLabel) {
|
||||
UILabel *lbl = [[UILabel alloc] init];
|
||||
lbl.numberOfLines = kMaxTextLines;
|
||||
lbl.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||
lbl.textAlignment = NSTextAlignmentLeft;
|
||||
[self addSubview:lbl];
|
||||
self.titleLabel = lbl;
|
||||
}
|
||||
|
||||
// 初始化为默认(未选中)样式
|
||||
[self _applyStyle:NO];
|
||||
}
|
||||
|
||||
/// 根据选中状态切换图标 & 文字样式
|
||||
- (void)_applyStyle:(BOOL)selected {
|
||||
if (selected) {
|
||||
self.iconView.image = [AMapNavCommonUtil imageWithName3x:@"station_select_icon"];
|
||||
self.titleLabel.font = [UIFont boldSystemFontOfSize:14];
|
||||
self.titleLabel.textColor = [UIColor colorWithWhite:0.12 alpha:1.0];
|
||||
} else {
|
||||
self.iconView.image = [AMapNavCommonUtil imageWithName3x:@"station_normal_icon"];
|
||||
self.titleLabel.font = [UIFont systemFontOfSize:13];
|
||||
self.titleLabel.textColor = [UIColor colorWithWhite:0.25 alpha:1.0];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
- (void)_updateFromAnnotation {
|
||||
NSString *text = nil;
|
||||
id<MAAnnotation> ann = self.annotation;
|
||||
if ([ann respondsToSelector:@selector(title)]) {
|
||||
text = ann.title;
|
||||
}
|
||||
if (text.length == 0) {
|
||||
text = @"加氢站";
|
||||
}
|
||||
self.titleLabel.text = text;
|
||||
}
|
||||
|
||||
#pragma mark - Selection(MAAnnotationView 官方选中回调)
|
||||
|
||||
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
|
||||
[super setSelected:selected animated:animated];
|
||||
self.isAnnotationSelected = selected;
|
||||
[self _applyStyle:selected];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
// 文字最大 100pt、最多 2 行
|
||||
CGSize textConstraint = CGSizeMake(kMaxTextWidth,
|
||||
CGFLOAT_MAX);
|
||||
CGSize textFit = [self.titleLabel sizeThatFits:textConstraint];
|
||||
// 高度上限:2 行
|
||||
CGFloat lineH = self.titleLabel.font.lineHeight;
|
||||
CGFloat maxTextH = lineH * kMaxTextLines
|
||||
+ self.titleLabel.font.leading * (kMaxTextLines - 1);
|
||||
CGFloat textW = MIN(textFit.width, kMaxTextWidth);
|
||||
CGFloat textH = MIN(textFit.height, maxTextH);
|
||||
|
||||
CGFloat totalW = kIconSize + kIconTextGap + textW;
|
||||
CGFloat totalH = MAX(kIconSize, textH) + kVPad * 2;
|
||||
|
||||
// 更新自身 bounds
|
||||
self.bounds = CGRectMake(0, 0, totalW, totalH);
|
||||
|
||||
// 图标:垂直居中
|
||||
CGFloat iconY = (totalH - kIconSize) * 0.5;
|
||||
self.iconView.frame = CGRectMake(0, iconY, kIconSize, kIconSize);
|
||||
|
||||
// 文字:垂直居中
|
||||
CGFloat textY = (totalH - textH) * 0.5;
|
||||
self.titleLabel.frame = CGRectMake(kIconSize + 1, textY, textW, textH);
|
||||
|
||||
// 锚点:让图标底部对齐地图坐标点
|
||||
// MAAnnotationView 的 centerOffset 以 (0,0)=中心 为原点
|
||||
// 默认选中图标底部对准坐标:向上偏移 totalH/2
|
||||
self.centerOffset = CGPointMake(totalW / 2.0 - kIconSize / 2.0,
|
||||
-(totalH / 2.0));
|
||||
}
|
||||
|
||||
#pragma mark - Hit Test
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
UIView *hit = [super hitTest:point withEvent:event];
|
||||
if (hit == self || [hit isDescendantOfView:self]) {
|
||||
return self;
|
||||
}
|
||||
return hit;
|
||||
}
|
||||
|
||||
#pragma mark - Touch
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
[super touchesBegan:touches withEvent:event];
|
||||
|
||||
id<MAAnnotation> annotation = self.annotation;
|
||||
if (!annotation) return;
|
||||
|
||||
UIView *sv = self.superview;
|
||||
while (sv && ![sv isKindOfClass:[MAMapView class]]) {
|
||||
sv = sv.superview;
|
||||
}
|
||||
MAMapView *mapView = (MAMapView *)sv;
|
||||
if (!mapView) return;
|
||||
|
||||
// [mapView deselectAnnotation:annotation animated:NO];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// ACustomNaviDriveController.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/4/11.
|
||||
//
|
||||
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ACustomNaviDriveController : ABaseViewController
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// ACustomNaviDriveController.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/4/11.
|
||||
//
|
||||
|
||||
#import "ACustomNaviDriveController.h"
|
||||
|
||||
@interface ACustomNaviDriveController ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation ACustomNaviDriveController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// AMapNavSDKHeader.h
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/2/10.
|
||||
//
|
||||
|
||||
#ifndef AMapNavSDKHeader_h
|
||||
#define AMapNavSDKHeader_h
|
||||
|
||||
//#define kAMapSDKDebugFlag
|
||||
// iPhone X
|
||||
#define AMP_iPhoneX (UIApplication.sharedApplication.keyWindow.safeAreaInsets.bottom > 0 ? YES : NO)
|
||||
|
||||
// Status bar height.
|
||||
#define AMP_StatusBarHeight (AMP_iPhoneX ? 44.f : 20.f)
|
||||
|
||||
// Navigation bar height.
|
||||
#define AMP_NavigationBarHeight 44.f
|
||||
|
||||
// Tabbar height.
|
||||
#define AMP_TabbarHeight (AMP_iPhoneX ? (49.f+34.f + 5) : 49.f)
|
||||
|
||||
// Tabbar safe bottom margin.
|
||||
#define AMP_TabbarSafeBottomMargin (AMP_iPhoneX ? 34.f : 0.f)
|
||||
|
||||
|
||||
#pragma mark - url
|
||||
///获取站点列表
|
||||
#define kGetStationListUrl @"https://beta-esg.api.lnh2e.com/appointment/station/getNearbyHydrogenStationsByLocation"
|
||||
|
||||
///单个站点详情
|
||||
#define kGetStationDetailtUrl @"https://beta-esg.api.lnh2e.com/appointment/station/getStationInfoByArea"
|
||||
|
||||
|
||||
///获取途经点
|
||||
/**
|
||||
请求方式:post 暂时调不通
|
||||
{
|
||||
"longitude":"121.254139",
|
||||
"latitude":"31.214628",
|
||||
"plateNumber":"浙F32111F",
|
||||
"hydrogenSiteId":""//加氢站DI}
|
||||
|
||||
*/
|
||||
#define kGetRoutePointtUrl @"https://beta-esg.api.lnh2e.com/appointment/truck/truckRouteAlgorithm"
|
||||
|
||||
|
||||
#import "ANavPointModel.h"
|
||||
|
||||
#endif /* AMapNavSDKHeader_h */
|
||||
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// AMapNavSDKManager.h
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/2/10.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARoutePlaneController.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AMapNavSDKManager : NSObject
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
@property (nonatomic , strong) NSString * localCity;
|
||||
@property (nonatomic, copy) NSString * locationAddressDetail;
|
||||
|
||||
@property (nonatomic , strong , readonly) UIViewController * targetVC;
|
||||
|
||||
+ (instancetype)sharedManager;
|
||||
- (void)configWithKey:(NSString*)key;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// AMapNavSDKManager.m
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/2/10.
|
||||
//
|
||||
|
||||
#import "AMapNavSDKManager.h"
|
||||
#import "AMapPrivacyUtility.h"
|
||||
|
||||
#import <AMapFoundationKit/AMapFoundationKit.h>
|
||||
|
||||
@interface AMapNavSDKManager ()
|
||||
@property (nonatomic , strong , readwrite) UIViewController * targetVC;
|
||||
@end
|
||||
|
||||
@implementation AMapNavSDKManager
|
||||
|
||||
+ (instancetype)sharedManager {
|
||||
static AMapNavSDKManager *manager = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[AMapNavSDKManager alloc] init];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_targetVC = [ARoutePlaneController new];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void)configWithKey:(NSString*)key {
|
||||
if (1) {
|
||||
/*
|
||||
* 调用隐私合规处理方法
|
||||
*/
|
||||
// [AMapPrivacyUtility handlePrivacyAgreeStatusIn:_targetVC];
|
||||
//
|
||||
// 初始化高德导航SDK
|
||||
[self configureAPIKey:key];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
- (UIViewController *)targetVC {
|
||||
return _targetVC;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
- (void)configureAPIKey:(NSString*)key {
|
||||
if ([key length] == 0)
|
||||
{
|
||||
NSString *reason = [NSString stringWithFormat:@"apiKey为空,请检查key是否正确设置。"];
|
||||
|
||||
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:reason delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
|
||||
|
||||
[alert show];
|
||||
}
|
||||
[AMapServices sharedServices].enableHTTPS = YES;
|
||||
[AMapServices sharedServices].apiKey = (NSString *)key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// ARoutePlaneController.h
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/5.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
#import "SelectableOverlay.h"
|
||||
#import "NaviPointAnnotation.h"
|
||||
|
||||
#import <AMapNaviKit/AMapNaviKit.h>
|
||||
|
||||
#import "ACustomAnnotationView.h"
|
||||
#import "AMapNavSDKHeader.h"
|
||||
#import "ACustomPointAnnotation.h"
|
||||
#import "ATripCalcResponse.h"
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ARoutePlaneController : ABaseViewController<AMapNaviDriveDataRepresentable>
|
||||
@property (nonatomic , strong) AMapNaviDriveView * driveView;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// ASearchAddressController.h
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/6.
|
||||
//
|
||||
|
||||
#import "ABaseViewController.h"
|
||||
|
||||
#import <AMapSearchKit/AMapSearchKit.h>
|
||||
#import <AMapLocationKit/AMapLocationKit.h>
|
||||
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ASearchAddressController : ABaseViewController
|
||||
|
||||
@property (nonatomic , copy) void(^selectAddressBlk)(AMapPOI * poi);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,289 @@
|
||||
//
|
||||
// ASearchAddressController.m
|
||||
// ANavDemo
|
||||
//
|
||||
// Created by admin on 2026/2/6.
|
||||
//
|
||||
|
||||
#import "ASearchAddressController.h"
|
||||
#import "AMapNavSDKManager.h"
|
||||
|
||||
#import <AMapFoundationKit/AMapFoundationKit.h>
|
||||
|
||||
#import "AMapNavCommonUtil.h"
|
||||
|
||||
@interface ASearchAddressController () <UITextFieldDelegate, AMapSearchDelegate, UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@property(nonatomic, strong) UITableView *tableView;
|
||||
@property(nonatomic, strong) UIBarButtonItem *rightItem;
|
||||
@property(nonatomic, strong) UIButton *backBtn;
|
||||
|
||||
@property(nonatomic, strong) NSMutableArray *dataArr;
|
||||
|
||||
@property(nonatomic, strong) UITextField *inputAddressTf;
|
||||
@property(nonatomic, strong) AMapSearchAPI *search;
|
||||
@property(nonatomic, assign) NSInteger currPage;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASearchAddressController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
// Do any additional setup after loading the view.
|
||||
self.title = @"选择地点";
|
||||
self.currPage = 1;
|
||||
_dataArr = [NSMutableArray array];
|
||||
|
||||
|
||||
[self initSubview];
|
||||
|
||||
self.search = [[AMapSearchAPI alloc] init];
|
||||
self.search.delegate = self;
|
||||
|
||||
#ifdef kAMapSDKDebugFlag
|
||||
self.inputAddressTf.text = @"人民广场";
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self.inputAddressTf becomeFirstResponder];
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
- (void)initSubview {
|
||||
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:self.backBtn];
|
||||
|
||||
UITextField *inputAddressTf = [[UITextField alloc] init];
|
||||
inputAddressTf.borderStyle = UITextBorderStyleRoundedRect;
|
||||
inputAddressTf.placeholder = @"输入地址";
|
||||
inputAddressTf.returnKeyType = UIReturnKeySearch;
|
||||
inputAddressTf.tag = 100;
|
||||
inputAddressTf.delegate = self;
|
||||
|
||||
[self.view addSubview:inputAddressTf];
|
||||
|
||||
[inputAddressTf mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.mas_equalTo(self.view).offset(10);
|
||||
make.top.mas_equalTo(self.view).offset(kRoutePlanBarHeight + 10);
|
||||
make.height.mas_equalTo(@30);
|
||||
}];
|
||||
|
||||
self.inputAddressTf = inputAddressTf;
|
||||
|
||||
|
||||
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[btn setTitle:@"当前位置" forState:UIControlStateNormal];
|
||||
btn.backgroundColor = [UIColor whiteColor];
|
||||
btn.layer.borderColor = [UIColor blueColor].CGColor;
|
||||
btn.layer.borderWidth = 1;
|
||||
btn.layer.cornerRadius = 5;
|
||||
btn.titleLabel.font = [UIFont systemFontOfSize:12];
|
||||
btn.hidden = YES;
|
||||
|
||||
[btn setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
|
||||
[btn addTarget:self action:@selector(searchBtnAction) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:btn];
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(inputAddressTf.mas_right).offset(12);
|
||||
make.top.mas_equalTo(inputAddressTf);
|
||||
make.right.mas_equalTo(self.view).offset(-10);
|
||||
make.height.mas_equalTo(@30);
|
||||
make.width.mas_equalTo(70);
|
||||
}];
|
||||
|
||||
|
||||
[self.view addSubview:self.tableView];
|
||||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.mas_equalTo(inputAddressTf.mas_bottom).offset(5);
|
||||
make.left.equalTo(self.view);
|
||||
make.bottom.equalTo(self.view);
|
||||
make.centerX.equalTo(self.view);
|
||||
|
||||
}];
|
||||
|
||||
[self.tableView reloadData];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (nonnull UITableViewCell
|
||||
|
||||
*)tableView:(
|
||||
nonnull UITableView
|
||||
*)
|
||||
tableView cellForRowAtIndexPath
|
||||
:(
|
||||
nonnull NSIndexPath
|
||||
*)indexPath {
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
|
||||
AMapPOI *m = self.dataArr[indexPath.row];
|
||||
cell.textLabel.text = [NSString stringWithFormat:@"%@", m.name];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(nonnull UITableView
|
||||
|
||||
*)
|
||||
tableView numberOfRowsInSection
|
||||
:(NSInteger)section {
|
||||
return self.dataArr.count;
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
AMapPOI *m = self.dataArr[indexPath.row];
|
||||
|
||||
if (self.selectAddressBlk) {
|
||||
self.selectAddressBlk(m);
|
||||
}
|
||||
|
||||
[self backBtnAction];
|
||||
// [self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if (indexPath.row == self.dataArr.count - 1 && (self.dataArr.count % 20 == 0)) {
|
||||
self.currPage = self.currPage + 1;
|
||||
[self requestAddressWithAddress:self.inputAddressTf.text atPage:self.currPage];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (UITableView *)tableView {
|
||||
if (!_tableView) {
|
||||
_tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
|
||||
_tableView.delegate = self;
|
||||
_tableView.dataSource = self;
|
||||
}
|
||||
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
- (UIButton *)backBtn {
|
||||
if (!_backBtn) {
|
||||
_backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_backBtn.frame = CGRectMake(0, 0, 40, 30);
|
||||
_backBtn.imageEdgeInsets = UIEdgeInsetsMake(2, -5, 2, 5);
|
||||
|
||||
// _backBtn.backgroundColor = UIColor.redColor;
|
||||
[_backBtn setImage:[AMapNavCommonUtil imageWithName:@"icon_fanhui"] forState:UIControlStateNormal];
|
||||
[_backBtn addTarget:self action:@selector(backBtnAction) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _backBtn;
|
||||
}
|
||||
|
||||
- (void)backBtnAction {
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
|
||||
}];
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Action
|
||||
|
||||
- (void)searchBtnAction {
|
||||
AMapNavSDKManager *sdk = [AMapNavSDKManager sharedManager];
|
||||
self.inputAddressTf.text = sdk.locationAddressDetail;
|
||||
|
||||
[self startSearchWithAddress:self.inputAddressTf.text];
|
||||
}
|
||||
|
||||
- (void)startSearchWithAddress:(NSString *)addr {
|
||||
if (!addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init];
|
||||
|
||||
request.keywords = addr;
|
||||
|
||||
AMapNavSDKManager *sdk = [AMapNavSDKManager sharedManager];
|
||||
request.city = sdk.localCity;
|
||||
|
||||
|
||||
// request.types = @"高等院校";
|
||||
// request.requireExtension = YES;
|
||||
request.offset = 20;
|
||||
request.page = 1;
|
||||
|
||||
/* 搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
|
||||
// request.cityLimit = YES;
|
||||
// request.requireSubPOIs = YES;
|
||||
|
||||
|
||||
[self.search AMapPOIKeywordsSearch:request];
|
||||
|
||||
}
|
||||
|
||||
- (void)requestAddressWithAddress:(NSString *)addr atPage:(NSInteger)page {
|
||||
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc] init];
|
||||
|
||||
request.keywords = addr;
|
||||
|
||||
AMapNavSDKManager *sdk = [AMapNavSDKManager sharedManager];
|
||||
request.city = sdk.localCity;
|
||||
|
||||
|
||||
// request.types = @"高等院校";
|
||||
// request.requireExtension = YES;
|
||||
request.offset = 20;
|
||||
request.page = page;
|
||||
|
||||
/* 搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
|
||||
request.cityLimit = YES;
|
||||
// request.requireSubPOIs = YES;
|
||||
|
||||
|
||||
[self.search AMapPOIKeywordsSearch:request];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/* POI 搜索回调. */
|
||||
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response {
|
||||
NSArray *pois = response.pois;
|
||||
if (pois.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//解析response获取POI信息,具体解析见 Demo
|
||||
|
||||
[self.dataArr addObjectsFromArray:pois];
|
||||
|
||||
[self.tableView reloadData];
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
|
||||
[textField resignFirstResponder];
|
||||
[self.dataArr removeAllObjects];
|
||||
self.currPage = 1;
|
||||
|
||||
[self startSearchWithAddress:textField.text];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)touchesBegan:(NSSet
|
||||
|
||||
<UITouch *> *)
|
||||
touches withEvent
|
||||
:(UIEvent *)event {
|
||||
[self.inputAddressTf resignFirstResponder];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// AStationDetailPopupController.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/22.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "ANavPointModel.h"
|
||||
#import "AMapNavSDKHeader.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class AStationDetailPopupController;
|
||||
|
||||
@protocol AStationDetailPopupDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/// 点击"开始导航"
|
||||
- (void)stationDetailPopupDidTapStartNavi:(AStationDetailPopupController *)popup;
|
||||
/// 点击关闭
|
||||
- (void)stationDetailPopupDidTapClose:(AStationDetailPopupController *)popup;
|
||||
|
||||
@end
|
||||
|
||||
@interface AStationDetailPopupController : UIViewController
|
||||
|
||||
@property (nonatomic, strong, nullable) ANavPointModel *pointModel;
|
||||
|
||||
/// 预计加氢费用(元),可由外部传入;若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *estimatedCost;
|
||||
|
||||
/// 预计时间,如 @"15分钟";若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *estimatedTime;
|
||||
|
||||
/// 行驶里程,如 @"23.5公里";若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *driveDistance;
|
||||
|
||||
/// 过路费,如 @"30元";若 nil 则隐藏
|
||||
@property (nonatomic, copy, nullable) NSString *tollFee;
|
||||
|
||||
/// 营业时间,如 @"00:00-24:00";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *businessHours;
|
||||
|
||||
/// 站点联系人,如 @"陈凯";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *contactName;
|
||||
|
||||
/// 联系方式,如 @"18019187371";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *contactPhone;
|
||||
|
||||
/// 加氢价格,如 @"32元/L";有值显示,无值显示 -
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenPrice;
|
||||
|
||||
|
||||
@property (nonatomic, weak, nullable) id<AStationDetailPopupDelegate> delegate;
|
||||
|
||||
/// 以半透明蒙层方式弹出在目标控制器上
|
||||
- (void)presentInViewController:(UIViewController *)parentVC;
|
||||
|
||||
/// 关闭弹框
|
||||
- (void)dismiss;
|
||||
|
||||
/// 关闭弹框,动画结束后执行 completion(用于关闭后再 present 其他页面)
|
||||
- (void)dismissWithCompletion:(nullable void(^)(void))completion;
|
||||
|
||||
|
||||
-(void)resetUI;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,654 @@
|
||||
//
|
||||
// AStationDetailPopupController.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/22.
|
||||
//
|
||||
|
||||
#import "AStationDetailPopupController.h"
|
||||
#import "ABaseViewController.h"
|
||||
#import "AMapNavCommonUtil.h"
|
||||
#import <Masonry/Masonry.h>
|
||||
|
||||
// 主题绿色(开始导航按钮背景)
|
||||
static inline UIColor *AStationThemeGreen(void) {
|
||||
return [UIColor colorWithRed:0x1A/255.0 green:0x7C/255.0 blue:0x43/255.0 alpha:1.0];
|
||||
}
|
||||
|
||||
@interface AStationDetailPopupController ()
|
||||
|
||||
/// 背景蒙层
|
||||
@property (nonatomic, strong) UIControl *maskControl;
|
||||
|
||||
/// 弹框卡片容器
|
||||
@property (nonatomic, strong) UIView *cardView;
|
||||
|
||||
/// 站点名称
|
||||
@property (nonatomic, strong) UILabel *stationNameLabel;
|
||||
|
||||
/// 预计加氢费用(名称右侧)
|
||||
@property (nonatomic, strong) UIImageView *costIconView;
|
||||
@property (nonatomic, strong) UILabel *costLabel;
|
||||
|
||||
/// 地址
|
||||
@property (nonatomic, strong) UILabel *addressLabel;
|
||||
|
||||
/// 分割线
|
||||
@property (nonatomic, strong) UIView *separator;
|
||||
|
||||
/// 预计时间行
|
||||
@property (nonatomic, strong) UIImageView *timeIconView;
|
||||
@property (nonatomic, strong) UILabel *timeLabel;
|
||||
|
||||
/// 行驶里程
|
||||
@property (nonatomic, strong) UIImageView *distanceIconView;
|
||||
@property (nonatomic, strong) UILabel *distanceLabel;
|
||||
|
||||
/// 过路费
|
||||
@property (nonatomic, strong) UIImageView *tollIconView;
|
||||
@property (nonatomic, strong) UILabel *tollLabel;
|
||||
|
||||
/// 关闭按钮
|
||||
@property (nonatomic, strong) UIButton *closeButton;
|
||||
|
||||
/// 开始导航按钮
|
||||
@property (nonatomic, strong) UIButton *startNaviButton;
|
||||
|
||||
/// 卡片 bottom constraint(动画用)
|
||||
@property (nonatomic, strong) MASConstraint *cardBottomConstraint;
|
||||
|
||||
// ── 营业时间 ──
|
||||
@property (nonatomic, strong) UILabel *businessHoursLabel;
|
||||
|
||||
// ── 站点联系人 ──
|
||||
@property (nonatomic, strong) UIImageView *contactPersonIconView;
|
||||
@property (nonatomic, strong) UILabel *contactPersonLabel;
|
||||
|
||||
// ── 加氢价格 ──
|
||||
@property (nonatomic, strong) UIImageView *priceIconView;
|
||||
@property (nonatomic, strong) UILabel *priceLabel;
|
||||
|
||||
// ── 联系方式 ──
|
||||
@property (nonatomic, strong) UIImageView *phoneIconView;
|
||||
@property (nonatomic, strong) UILabel *phoneLabel;
|
||||
|
||||
@end
|
||||
|
||||
@implementation AStationDetailPopupController
|
||||
|
||||
#pragma mark - Life Cycle
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
[self _buildUI];
|
||||
[self _setupMasonryConstraints];
|
||||
[self _updateUI];
|
||||
}
|
||||
|
||||
- (void)viewDidAppear:(BOOL)animated {
|
||||
[super viewDidAppear:animated];
|
||||
[self _playShowAnimation];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)presentInViewController:(UIViewController *)parentVC {
|
||||
self.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
[parentVC presentViewController:self animated:NO completion:nil];
|
||||
}
|
||||
|
||||
- (void)dismiss {
|
||||
[self dismissWithCompletion:nil];
|
||||
}
|
||||
|
||||
- (void)dismissWithCompletion:(void(^)(void))completion {
|
||||
[self _playDismissAnimationWithCompletion:^{
|
||||
[self dismissViewControllerAnimated:NO completion:completion];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Setter Override(弹出前赋值 或 弹出后动态更新)
|
||||
|
||||
- (void)setPointModel:(ANavPointModel *)pointModel {
|
||||
_pointModel = pointModel;
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setEstimatedCost:(NSString *)estimatedCost {
|
||||
_estimatedCost = [estimatedCost copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setEstimatedTime:(NSString *)estimatedTime {
|
||||
_estimatedTime = [estimatedTime copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setDriveDistance:(NSString *)driveDistance {
|
||||
_driveDistance = [driveDistance copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setTollFee:(NSString *)tollFee {
|
||||
_tollFee = [tollFee copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setBusinessHours:(NSString *)businessHours {
|
||||
_businessHours = [businessHours copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setContactName:(NSString *)contactName {
|
||||
_contactName = [contactName copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setContactPhone:(NSString *)contactPhone {
|
||||
_contactPhone = [contactPhone copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
- (void)setHydrogenPrice:(NSString *)hydrogenPrice {
|
||||
_hydrogenPrice = [hydrogenPrice copy];
|
||||
if (self.isViewLoaded) [self _updateUI];
|
||||
}
|
||||
|
||||
#pragma mark - Build UI
|
||||
|
||||
/**
|
||||
卡片结构:
|
||||
┌──────────────────────────────────── [✕] ┐
|
||||
│ 站点名称(加粗) 预计加氢费用:--元 │ ← 同行,间距20pt
|
||||
│ 地址(灰色小字,多行) │
|
||||
│ ──────────────────────────────────────── │
|
||||
│ [icon] 预计时间:-- 分钟 │
|
||||
│ [icon] 行驶里程:-- 公里 [icon] 过路费:-- 元 │
|
||||
│ ╔════════════════════════════════════╗ │
|
||||
│ ║ 开始导航 ║ │
|
||||
│ ╚════════════════════════════════════╝ │
|
||||
│ (距底部30pt) │
|
||||
└─────────────────────────────────────────┘
|
||||
*/
|
||||
- (void)_buildUI {
|
||||
// ── 蒙层 ──
|
||||
UIControl *mask = [[UIControl alloc] init];
|
||||
mask.backgroundColor = [UIColor colorWithWhite:0 alpha:0.35];
|
||||
[mask addTarget:self action:@selector(_onMaskTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:mask];
|
||||
self.maskControl = mask;
|
||||
|
||||
// ── 卡片 ──
|
||||
UIView *card = [[UIView alloc] init];
|
||||
card.backgroundColor = [UIColor whiteColor];
|
||||
card.layer.cornerRadius = 16;
|
||||
card.layer.masksToBounds = NO;
|
||||
card.layer.shadowColor = [UIColor blackColor].CGColor;
|
||||
card.layer.shadowOpacity = 0.15;
|
||||
card.layer.shadowRadius = 12;
|
||||
card.layer.shadowOffset = CGSizeMake(0, -4);
|
||||
[self.view addSubview:card];
|
||||
self.cardView = card;
|
||||
|
||||
// ── 关闭按钮 ──
|
||||
UIButton *closeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
// [closeBtn setTitle:@"✕" forState:UIControlStateNormal];
|
||||
[closeBtn setImage: [AMapNavCommonUtil imageWithName:@"icon_close"] forState:UIControlStateNormal];
|
||||
|
||||
[closeBtn setTitleColor:[UIColor colorWithWhite:0.5 alpha:1] forState:UIControlStateNormal];
|
||||
closeBtn.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[closeBtn addTarget:self action:@selector(_onCloseTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
[card addSubview:closeBtn];
|
||||
self.closeButton = closeBtn;
|
||||
|
||||
UIColor * headTextColor = [UIColor hp_colorWithRGBHex:0x1D2129];
|
||||
UIFont * headTextFont = [UIFont hp_pingFangMedium:14];
|
||||
|
||||
// ── 站点名称 ──
|
||||
UILabel *nameLabel = [[UILabel alloc] init];
|
||||
nameLabel.font = [UIFont hp_pingFangMedium:18];
|
||||
nameLabel.textColor = headTextColor;
|
||||
nameLabel.numberOfLines = 2;
|
||||
// nameLabel.adjustsFontSizeToFitWidth = YES;
|
||||
nameLabel.minimumScaleFactor = 0.8;
|
||||
[card addSubview:nameLabel];
|
||||
self.stationNameLabel = nameLabel;
|
||||
|
||||
// ── 预计加氢费用(名称右侧,间距20pt) ──
|
||||
UILabel *costLabel = [[UILabel alloc] init];
|
||||
costLabel.font = headTextFont;
|
||||
costLabel.textColor = headTextColor;
|
||||
costLabel.numberOfLines = 1;
|
||||
costLabel.textAlignment = NSTextAlignmentLeft;
|
||||
// [costLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
// [costLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[card addSubview:costLabel];
|
||||
self.costLabel = costLabel;
|
||||
|
||||
// ── 营业时间(站点名称下方,4pt间距,浅灰色小字) ──
|
||||
UILabel *bizHoursLabel = [[UILabel alloc] init];
|
||||
bizHoursLabel.font = [UIFont hp_pingFangRegular:14];
|
||||
bizHoursLabel.textColor = [UIColor hp_colorWithRGBHex:0x1D2129];
|
||||
bizHoursLabel.numberOfLines = 1;
|
||||
[card addSubview:bizHoursLabel];
|
||||
self.businessHoursLabel = bizHoursLabel;
|
||||
|
||||
// ── 地址 ──
|
||||
UILabel *addrLabel = [[UILabel alloc] init];
|
||||
addrLabel.font = [UIFont hp_pingFangRegular:14];
|
||||
addrLabel.textColor = [UIColor hp_colorWithRGBHex:0x86909C];
|
||||
addrLabel.numberOfLines = 2;
|
||||
[card addSubview:addrLabel];
|
||||
self.addressLabel = addrLabel;
|
||||
|
||||
UIImageView *costIcon = [[UIImageView alloc] init];
|
||||
costIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
costIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_fuel"];
|
||||
[card addSubview:costIcon];
|
||||
self.costIconView = costIcon;
|
||||
|
||||
// ── 分割线 ──
|
||||
UIView *sep = [[UIView alloc] init];
|
||||
sep.backgroundColor = [UIColor colorWithWhite:0.88 alpha:1];
|
||||
[card addSubview:sep];
|
||||
self.separator = sep;
|
||||
|
||||
// ── 预计时间图标 ──
|
||||
UIImageView *timeIcon = [[UIImageView alloc] init];
|
||||
timeIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
timeIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_time"];
|
||||
[card addSubview:timeIcon];
|
||||
self.timeIconView = timeIcon;
|
||||
|
||||
// ── 预计时间文字 ──
|
||||
UILabel *timeLabel = [[UILabel alloc] init];
|
||||
timeLabel.font = headTextFont;
|
||||
timeLabel.textColor = headTextColor;
|
||||
[card addSubview:timeLabel];
|
||||
self.timeLabel = timeLabel;
|
||||
|
||||
// ── 行驶里程图标 ──
|
||||
UIImageView *distIcon = [[UIImageView alloc] init];
|
||||
distIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
distIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_mileage"];
|
||||
[card addSubview:distIcon];
|
||||
self.distanceIconView = distIcon;
|
||||
|
||||
// ── 行驶里程文字 ──
|
||||
UILabel *distLabel = [[UILabel alloc] init];
|
||||
distLabel.font = headTextFont;
|
||||
distLabel.textColor = headTextColor;
|
||||
[card addSubview:distLabel];
|
||||
self.distanceLabel = distLabel;
|
||||
|
||||
// ── 过路费图标 ──
|
||||
UIImageView *tollIcon = [[UIImageView alloc] init];
|
||||
tollIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
tollIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_toll"];
|
||||
[card addSubview:tollIcon];
|
||||
self.tollIconView = tollIcon;
|
||||
|
||||
// ── 过路费文字 ──
|
||||
UILabel *tollLabel = [[UILabel alloc] init];
|
||||
tollLabel.font = headTextFont;
|
||||
tollLabel.textColor = headTextColor;
|
||||
[card addSubview:tollLabel];
|
||||
self.tollLabel = tollLabel;
|
||||
|
||||
// ── 站点联系人图标 & 文字 ──
|
||||
UIImageView *personIcon = [[UIImageView alloc] init];
|
||||
personIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
personIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_person"];
|
||||
[card addSubview:personIcon];
|
||||
self.contactPersonIconView = personIcon;
|
||||
|
||||
UILabel *personLabel = [[UILabel alloc] init];
|
||||
personLabel.font = headTextFont;
|
||||
personLabel.textColor = headTextColor;
|
||||
[card addSubview:personLabel];
|
||||
self.contactPersonLabel = personLabel;
|
||||
|
||||
// ── 加氢价格图标 & 文字 ──
|
||||
UIImageView *priceIcon = [[UIImageView alloc] init];
|
||||
priceIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
priceIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_price"];
|
||||
[card addSubview:priceIcon];
|
||||
self.priceIconView = priceIcon;
|
||||
|
||||
UILabel *priceLabel = [[UILabel alloc] init];
|
||||
priceLabel.font = headTextFont;
|
||||
priceLabel.textColor = headTextColor;
|
||||
[card addSubview:priceLabel];
|
||||
self.priceLabel = priceLabel;
|
||||
|
||||
// ── 联系方式图标 & 文字 ──
|
||||
UIImageView *phoneIcon = [[UIImageView alloc] init];
|
||||
phoneIcon.contentMode = UIViewContentModeScaleAspectFit;
|
||||
phoneIcon.image = [AMapNavCommonUtil imageWithName3x:@"ic_phone"];
|
||||
[card addSubview:phoneIcon];
|
||||
self.phoneIconView = phoneIcon;
|
||||
|
||||
UILabel *phoneLabel = [[UILabel alloc] init];
|
||||
phoneLabel.font = headTextFont;
|
||||
phoneLabel.textColor = headTextColor;
|
||||
[card addSubview:phoneLabel];
|
||||
self.phoneLabel = phoneLabel;
|
||||
|
||||
// ── 开始导航按钮 ──
|
||||
UIButton *naviBtn = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[naviBtn setTitle:@"开始导航" forState:UIControlStateNormal];
|
||||
[naviBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
naviBtn.titleLabel.font = [UIFont boldSystemFontOfSize:17];
|
||||
naviBtn.backgroundColor = AStationThemeGreen();
|
||||
naviBtn.layer.cornerRadius = 24;
|
||||
[naviBtn addTarget:self action:@selector(_onStartNaviTapped) forControlEvents:UIControlEventTouchUpInside];
|
||||
[card addSubview:naviBtn];
|
||||
self.startNaviButton = naviBtn;
|
||||
}
|
||||
|
||||
#pragma mark - Masonry Constraints
|
||||
|
||||
- (void)_setupMasonryConstraints {
|
||||
UIView *card = self.cardView;
|
||||
CGFloat iconSize = 16;
|
||||
|
||||
// ── 蒙层:铺满父视图 ──
|
||||
[self.maskControl mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.view);
|
||||
}];
|
||||
|
||||
// ── 卡片:左右各16,底部距 safeArea 30pt ──
|
||||
// 初始状态卡片在屏幕下方(动画起点),top 留空,先用 bottom 约束做弹入
|
||||
[card mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.view).offset(16);
|
||||
make.right.equalTo(self.view).offset(-16);
|
||||
// 动画起点:卡片顶部 = 父视图底部(完全隐藏在屏幕外)
|
||||
make.top.equalTo(self.view.mas_bottom).offset(0);
|
||||
}];
|
||||
|
||||
// ── 关闭按钮:右上角 ──
|
||||
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(card).offset(8);
|
||||
make.right.equalTo(card).offset(-15);
|
||||
make.width.height.mas_equalTo(40);
|
||||
}];
|
||||
|
||||
// ── 第一行:站点名称(左,内容自适应)+ 预计加氢费用(紧跟nameLabel右侧20pt) ──
|
||||
// nameLabel:内容有多宽占多宽,宽度上限55%,防止过长把费用挤掉
|
||||
[self.stationNameLabel setContentHuggingPriority:UILayoutPriorityDefaultLow
|
||||
forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[self.stationNameLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(card).offset(25);
|
||||
make.left.equalTo(card).offset(16);
|
||||
// make.width.lessThanOrEqualTo(card).multipliedBy(0.45);
|
||||
make.right.equalTo(self.closeButton.mas_left).offset(-12);
|
||||
}];
|
||||
|
||||
// ── 营业时间(站点名称下方,6pt间距) ──
|
||||
[self.businessHoursLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.stationNameLabel.mas_bottom).offset(10);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
}];
|
||||
|
||||
// ── 地址(营业时间下方,6pt间距) ──
|
||||
[self.addressLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.businessHoursLabel.mas_bottom).offset(10);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
}];
|
||||
self.separator.hidden = YES;
|
||||
|
||||
// ── 分割线(地址下方,12pt间距) ──
|
||||
[self.separator mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.addressLabel.mas_bottom).offset(10);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(0.5);
|
||||
}];
|
||||
|
||||
CGFloat _offset_y = 18;
|
||||
|
||||
// ── 预计时间行(分割线下方,14pt) ──
|
||||
[self.timeIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.separator.mas_bottom).offset(12);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.timeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.timeIconView);
|
||||
make.left.equalTo(self.timeIconView.mas_right).offset(6);
|
||||
// make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
///cost
|
||||
[self.costIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.timeIconView);
|
||||
make.left.equalTo(self.timeLabel.mas_right).offset(40);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.costLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.costIconView);
|
||||
make.left.equalTo(self.costIconView.mas_right).offset(6);
|
||||
make.right.lessThanOrEqualTo(card).offset(-10);
|
||||
}];
|
||||
|
||||
|
||||
// ── 行驶里程 + 过路费行(时间行下方,12pt) ──
|
||||
[self.distanceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.timeIconView.mas_bottom).offset(_offset_y);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.distanceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.distanceIconView);
|
||||
make.left.equalTo(self.distanceIconView.mas_right).offset(6);
|
||||
// 宽度约为可用区域的一半(另一半留给过路费)
|
||||
make.width.mas_lessThanOrEqualTo(130);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
[self.tollIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.distanceIconView);
|
||||
make.left.equalTo(self.costIconView.mas_left);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.tollLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.distanceIconView);
|
||||
make.left.equalTo(self.tollIconView.mas_right).offset(6);
|
||||
// make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 站点联系人行(过路费行下方,14pt) ──
|
||||
[self.contactPersonIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.distanceIconView.mas_bottom).offset(_offset_y);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.contactPersonLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.contactPersonIconView);
|
||||
make.left.equalTo(self.contactPersonIconView.mas_right).offset(6);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 加氢价格(与站点联系人同行,右侧对齐 costIconView) ──
|
||||
[self.priceIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.contactPersonIconView);
|
||||
make.left.equalTo(self.costIconView.mas_left);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.contactPersonIconView);
|
||||
make.left.equalTo(self.priceIconView.mas_right).offset(6);
|
||||
make.right.lessThanOrEqualTo(card).offset(-10);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 联系方式行(站点联系人行下方,12pt) ──
|
||||
[self.phoneIconView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.contactPersonIconView.mas_bottom).offset(_offset_y);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.width.height.mas_equalTo(iconSize);
|
||||
}];
|
||||
|
||||
[self.phoneLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerY.equalTo(self.phoneIconView);
|
||||
make.left.equalTo(self.phoneIconView.mas_right).offset(6);
|
||||
make.height.mas_equalTo(24);
|
||||
}];
|
||||
|
||||
// ── 开始导航按钮(联系方式行下方18pt,距卡片底部 safeArea) ──
|
||||
[self.startNaviButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.phoneIconView.mas_bottom).offset(40);
|
||||
make.left.equalTo(card).offset(16);
|
||||
make.right.equalTo(card).offset(-16);
|
||||
make.height.mas_equalTo(48);
|
||||
make.bottom.equalTo(card).offset(-AMP_TabbarSafeBottomMargin); // 卡片底部内间距30pt
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Data Update
|
||||
|
||||
- (void)_updateUI {
|
||||
self.stationNameLabel.text = (self.pointModel.name.length > 0)
|
||||
? self.pointModel.name : @"-";
|
||||
|
||||
// ── 营业时间 ──
|
||||
self.businessHoursLabel.text = (self.businessHours.length > 0)
|
||||
? [NSString stringWithFormat:@"营业时间:%@", self.businessHours]
|
||||
: @"营业时间:-";
|
||||
|
||||
self.costLabel.text = (self.estimatedCost.length > 0)
|
||||
? [NSString stringWithFormat:@"预计加氢费用:%@元", self.estimatedCost]
|
||||
: @"预计加氢费用:-";
|
||||
|
||||
self.addressLabel.text = (self.pointModel.address.length > 0)
|
||||
? self.pointModel.address : @"-";
|
||||
|
||||
// ── 预计时间(始终显示,无值显示"-- 分钟") ──
|
||||
self.timeLabel.text = (self.estimatedTime.length > 0)
|
||||
? [NSString stringWithFormat:@"预计时间:%@分钟", self.estimatedTime]
|
||||
: @"预计时间:-";
|
||||
|
||||
// ── 行驶里程(始终显示,无值显示"-- 公里") ──
|
||||
self.distanceLabel.text = (self.driveDistance.length > 0)
|
||||
? [NSString stringWithFormat:@"行驶里程:%@公里", self.driveDistance]
|
||||
: @"行驶里程:-";
|
||||
|
||||
// ── 过路费(始终显示,无值显示"-- 元") ──
|
||||
self.tollLabel.text = (self.tollFee.length > 0)
|
||||
? [NSString stringWithFormat:@"过路费:%@元", self.tollFee]
|
||||
: @"过路费:-";
|
||||
|
||||
// ── 站点联系人(有值显示值,无值显示 -) ──
|
||||
self.contactPersonLabel.text = (self.contactName.length > 0)
|
||||
? [NSString stringWithFormat:@"站联系人:%@", self.contactName]
|
||||
: @"站联系人:-";
|
||||
|
||||
// ── 加氢价格(有值显示值,无值显示 -) ──
|
||||
self.priceLabel.text = (self.hydrogenPrice.length > 0)
|
||||
? [NSString stringWithFormat:@"加氢价格:%@/L", self.hydrogenPrice]
|
||||
: @"加氢价格:-";
|
||||
|
||||
// ── 联系方式(有值显示值,无值显示 -) ──
|
||||
self.phoneLabel.text = (self.contactPhone.length > 0)
|
||||
? [NSString stringWithFormat:@"联系方式:%@", self.contactPhone]
|
||||
: @"联系方式:-";
|
||||
}
|
||||
|
||||
// 重置 UI 状态
|
||||
-(void)resetUI {
|
||||
self.stationNameLabel.text = @"-";
|
||||
self.businessHoursLabel.text = @"营业时间:-";
|
||||
self.costLabel.text = @"预计加氢费用:-";
|
||||
self.addressLabel.text = @"地址:-";
|
||||
self.timeLabel.text = @"预计时间:-";
|
||||
self.distanceLabel.text = @"行驶里程:-";
|
||||
self.tollLabel.text = @"过路费:-";
|
||||
self.contactPersonLabel.text = @"站联系人:-";
|
||||
self.priceLabel.text = @"加氢价格:-";
|
||||
self.phoneLabel.text = @"联系方式:-";
|
||||
}
|
||||
|
||||
#pragma mark - Animation
|
||||
|
||||
/**
|
||||
弹入动画:更新 top 约束将卡片滑入至距底部 30pt(含 safeArea)
|
||||
*/
|
||||
- (void)_playShowAnimation {
|
||||
// 目标状态:卡片底部紧贴 safeArea 底部,再往上留 30pt
|
||||
[self.cardView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.view).offset(0);
|
||||
make.right.equalTo(self.view).offset(-0);
|
||||
make.bottom.equalTo(self.view).offset(0);
|
||||
}];
|
||||
|
||||
self.maskControl.alpha = 0;
|
||||
[UIView animateWithDuration:0.36
|
||||
delay:0
|
||||
usingSpringWithDamping:0.82
|
||||
initialSpringVelocity:0.5
|
||||
options:UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
self.maskControl.alpha = 1;
|
||||
[self.view layoutIfNeeded];
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)_playDismissAnimationWithCompletion:(void(^)(void))completion {
|
||||
[self.cardView mas_remakeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.view).offset(16);
|
||||
make.right.equalTo(self.view).offset(-16);
|
||||
make.top.equalTo(self.view.mas_bottom).offset(20);
|
||||
}];
|
||||
|
||||
[UIView animateWithDuration:0.25
|
||||
delay:0
|
||||
options:UIViewAnimationOptionCurveEaseIn
|
||||
animations:^{
|
||||
self.maskControl.alpha = 0;
|
||||
[self.view layoutIfNeeded];
|
||||
} completion:^(BOOL finished) {
|
||||
if (completion) completion();
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)_onMaskTapped {
|
||||
if ([self.delegate respondsToSelector:@selector(stationDetailPopupDidTapClose:)]) {
|
||||
[self.delegate stationDetailPopupDidTapClose:self];
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (void)_onCloseTapped {
|
||||
if ([self.delegate respondsToSelector:@selector(stationDetailPopupDidTapClose:)]) {
|
||||
[self.delegate stationDetailPopupDidTapClose:self];
|
||||
}
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
- (void)_onStartNaviTapped {
|
||||
// 先关闭弹框(等动画完全结束),再通知 delegate 弹出导航页
|
||||
// 避免两个 presentViewController 同时进行导致导航页面无法显示
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self dismissWithCompletion:^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if ([strongSelf.delegate respondsToSelector:@selector(stationDetailPopupDidTapStartNavi:)]) {
|
||||
[strongSelf.delegate stationDetailPopupDidTapStartNavi:strongSelf];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// AAlgorithmPathModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AAlgorithmPathModel : NSObject
|
||||
|
||||
/// 单程
|
||||
@property (nonatomic, copy, nullable) NSString *tripRecommendationId;
|
||||
@property (nonatomic, copy, nullable) NSString *staId;
|
||||
@property (nonatomic, copy, nullable) NSString *tripOneWayPathId;
|
||||
@property (nonatomic, copy, nullable) NSString *tripReturnPathId;
|
||||
|
||||
/// 里程(公里)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayDis;
|
||||
@property (nonatomic, copy, nullable) NSString *returnDis;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripDis;
|
||||
|
||||
/// 时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayTime;
|
||||
@property (nonatomic, copy, nullable) NSString *returnTime;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripTime;
|
||||
|
||||
/// 总成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripCost;
|
||||
|
||||
/// 人工成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayLaborCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnLaborCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripLaborCost;
|
||||
|
||||
/// 高速成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayChargerouteCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnChargerouteCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripChargerouteCost;
|
||||
|
||||
/// 氢耗(公斤)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayHydrogenConsumption;
|
||||
@property (nonatomic, copy, nullable) NSString *returnLaborHydrogenConsumption;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripHydrogenConsumption;
|
||||
|
||||
/// 氢气成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *oneWayHydrogenCost;
|
||||
@property (nonatomic, copy, nullable) NSString *returnLaborHydrogenCost;
|
||||
@property (nonatomic, copy, nullable) NSString *roundTripHydrogenCost;
|
||||
|
||||
/// 加氢站相关
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenCost; // 氢气总成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaServiceTime; // 站服务总时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaRefuelingTime;// 实际加油时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaQueueTime; // 排队时长(分钟)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaServiceTimeCost; // 站服务时间成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaRefuelingTimeCost;// 加油时间成本(元)
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenStaQueueTimeCost; // 排队时间成本(元)
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// AAlgorithmPathModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "AAlgorithmPathModel.h"
|
||||
|
||||
@implementation AAlgorithmPathModel
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// ACustomPointAnnotation.h
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <AMapNaviKit/AMapNaviKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ACustomPointAnnotation : MAPointAnnotation
|
||||
@property (nonatomic, copy, nullable) NSString *stationID;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,12 @@
|
||||
//
|
||||
// ACustomPointAnnotation.m
|
||||
// Pods
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "ACustomPointAnnotation.h"
|
||||
|
||||
@implementation ACustomPointAnnotation
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// AMapHyStationModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/2/11.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
{
|
||||
"name": "嘉兴经开站",
|
||||
"shortName": null,
|
||||
"siteNo": null,
|
||||
"city": null,
|
||||
"address": "嘉兴市秀洲区岗山路272号",
|
||||
"contact": "龚明伟",
|
||||
"phone": "18888888888",
|
||||
"type": null,
|
||||
"coOpMode": null,
|
||||
"booking": null,
|
||||
"siteStatus": 0,
|
||||
"startBusiness": "06:00:00",
|
||||
"endBusiness": "22:00:00",
|
||||
"billingMethod": null,
|
||||
"term": null,
|
||||
"remark": null,
|
||||
"longitude": "120.75972800",
|
||||
"latitude": "30.79962800"
|
||||
}
|
||||
*/
|
||||
@interface AMapHyStationModel : NSObject
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *ID;
|
||||
@property (nonatomic, copy, nullable) NSString *hydrogenId;
|
||||
@property (nonatomic, copy) NSString *name;
|
||||
@property (nonatomic, copy, nullable) NSString *shortName;
|
||||
@property (nonatomic, copy, nullable) NSString *siteNo;
|
||||
@property (nonatomic, copy, nullable) NSString *city;
|
||||
@property (nonatomic, copy, nullable) NSString *address;
|
||||
@property (nonatomic, copy, nullable) NSString *contact;
|
||||
@property (nonatomic, copy, nullable) NSString *phone;
|
||||
@property (nonatomic, copy, nullable) NSString *type;
|
||||
@property (nonatomic, copy, nullable) NSString *coOpMode;
|
||||
@property (nonatomic, strong, nullable) NSString * booking;
|
||||
@property (nonatomic, assign) NSInteger siteStatus;
|
||||
@property (nonatomic, copy, nullable) NSString *startBusiness;
|
||||
@property (nonatomic, copy, nullable) NSString *endBusiness;
|
||||
@property (nonatomic, copy, nullable) NSString *billingMethod;
|
||||
@property (nonatomic, copy, nullable) NSString *term;
|
||||
@property (nonatomic, copy, nullable) NSString *remark;
|
||||
@property (nonatomic, copy, nullable) NSString *longitude;
|
||||
@property (nonatomic, copy, nullable) NSString *latitude;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
{
|
||||
"code": 0,
|
||||
"status": true,
|
||||
"message": "success",
|
||||
"data": [],
|
||||
"time": "1770800256408",
|
||||
"error": null
|
||||
}
|
||||
*/
|
||||
|
||||
@interface AMapHyResponse : NSObject
|
||||
@property (nonatomic, assign) NSInteger code;
|
||||
@property (nonatomic, assign) NSInteger status;
|
||||
@property (nonatomic, copy, nullable) NSString *message;
|
||||
@property (nonatomic, copy, nullable) NSString *time;
|
||||
@property (nonatomic, copy, nullable) NSString *error;
|
||||
|
||||
@property(nonatomic , strong)NSArray <AMapHyStationModel * > * data;
|
||||
|
||||
@end
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// AMapHyStationModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/2/11.
|
||||
//
|
||||
|
||||
#import "AMapHyStationModel.h"
|
||||
|
||||
@implementation AMapHyStationModel
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName
|
||||
{
|
||||
return @{ @"ID" : @"id"};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation AMapHyResponse
|
||||
|
||||
+ (NSDictionary *)mj_objectClassInArray {
|
||||
return @{@"data" : AMapHyStationModel.class};
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// ANavPointModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ANavPointModel : NSObject
|
||||
@property (nonatomic, copy, nullable) NSString *name;
|
||||
@property (nonatomic, copy, nullable) NSString *address;
|
||||
@property (nonatomic, copy, nullable) NSString *stationID;
|
||||
|
||||
///经纬度
|
||||
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
|
||||
|
||||
///初始化
|
||||
+ (instancetype)instanceWithCoordinate:(CLLocationCoordinate2D)coordinate name:(NSString *)name address:(NSString *)address;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// ANavPointModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "ANavPointModel.h"
|
||||
|
||||
@implementation ANavPointModel
|
||||
///初始化
|
||||
+ (instancetype)instanceWithCoordinate:(CLLocationCoordinate2D)coordinate name:(NSString *)name address:(NSString *)address {
|
||||
ANavPointModel *instance = [[ANavPointModel alloc] init];
|
||||
if (instance) {
|
||||
instance.coordinate = coordinate;
|
||||
instance.name = name;
|
||||
instance.address = address;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// ANaviPathInfoModel.h
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MJExtension/MJExtension.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/**
|
||||
{
|
||||
"name": "浙江省嘉兴市平湖市乍浦镇滨海大道中国石化滨海大道加油加气站",
|
||||
"poiId": "",
|
||||
"coordinate": {
|
||||
"longitude": "121.070434",
|
||||
"latitude": "30.596124"
|
||||
}
|
||||
}
|
||||
*/
|
||||
@interface ANaviPathInfoModel : NSObject
|
||||
|
||||
@property (nonatomic, copy, nullable) NSString *name;
|
||||
@property (nonatomic, copy, nullable) NSString *poiId;
|
||||
@property (nonatomic, copy, nullable) NSString *longitude;
|
||||
@property (nonatomic, copy, nullable) NSString *latitude;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// ANaviPathInfoModel.m
|
||||
// AMapNavIOSSDK
|
||||
//
|
||||
// Created by admin on 2026/3/25.
|
||||
//
|
||||
|
||||
#import "ANaviPathInfoModel.h"
|
||||
|
||||
@implementation ANaviPathInfoModel
|
||||
|
||||
+ (NSDictionary *)mj_replacedKeyFromPropertyName {
|
||||
// JSON 中 coordinate 是嵌套对象,展开为 longitude/latitude
|
||||
return @{
|
||||
@"longitude": @"coordinate.longitude",
|
||||
@"latitude": @"coordinate.latitude"
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||