diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..efe8a43
Binary files /dev/null and b/.DS_Store differ
diff --git a/ln_jq_app/.claude/settings.local.json b/ln_jq_app/.claude/settings.local.json
new file mode 100644
index 0000000..f948202
--- /dev/null
+++ b/ln_jq_app/.claude/settings.local.json
@@ -0,0 +1,8 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(flutter build apk --debug 2>&1 | tail -15)",
+ "Bash(flutter build apk --debug 2>&1 | tail -10)"
+ ]
+ }
+}
diff --git a/ln_jq_app/android/app/build.gradle.kts b/ln_jq_app/android/app/build.gradle.kts
index 65f652d..17dd78b 100644
--- a/ln_jq_app/android/app/build.gradle.kts
+++ b/ln_jq_app/android/app/build.gradle.kts
@@ -68,3 +68,8 @@ 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")
+}
diff --git a/ln_jq_app/android/app/proguard-rules.pro b/ln_jq_app/android/app/proguard-rules.pro
index 6e10484..4cefaf4 100644
--- a/ln_jq_app/android/app/proguard-rules.pro
+++ b/ln_jq_app/android/app/proguard-rules.pro
@@ -58,4 +58,31 @@
-dontwarn org.android.agoo.**
-dontwarn anetwork.**
-dontwarn com.ut.**
--dontwarn com.ta.**
\ No newline at end of file
+-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.**{*;}
\ No newline at end of file
diff --git a/ln_jq_app/android/app/src/main/AndroidManifest.xml b/ln_jq_app/android/app/src/main/AndroidManifest.xml
index c23b807..e2a1e53 100644
--- a/ln_jq_app/android/app/src/main/AndroidManifest.xml
+++ b/ln_jq_app/android/app/src/main/AndroidManifest.xml
@@ -17,7 +17,12 @@
-
+
+
+
+
+
+
@@ -26,6 +31,25 @@
android:name="${applicationName}"
android:icon="@mipmap/logo"
android:label="小羚羚">
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/MainActivity.java b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/MainActivity.java
index 6b67b7e..379b71d 100644
--- a/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/MainActivity.java
+++ b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/MainActivity.java
@@ -1,6 +1,136 @@
package com.lnkj.ln_jq_app;
+import android.Manifest;
+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)
+ );
+ }
+
+ /**
+ * 获取当前系统版本需要申请的权限列表
+ */
+ private String[] getRequiredPermissions() {
+ List 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 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
+ 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();
+ }
+ }
+ }
}
diff --git a/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NativeMapFactory.java b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NativeMapFactory.java
new file mode 100644
index 0000000..9e8ff31
--- /dev/null
+++ b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NativeMapFactory.java
@@ -0,0 +1,37 @@
+package com.lnkj.ln_jq_app;
+
+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;
+ }
+}
diff --git a/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NativeMapView.java b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NativeMapView.java
new file mode 100644
index 0000000..41b9675
--- /dev/null
+++ b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NativeMapView.java
@@ -0,0 +1,1599 @@
+package com.lnkj.ln_jq_app;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.amap.api.location.AMapLocation;
+import com.amap.api.location.AMapLocationClient;
+import com.amap.api.location.AMapLocationClientOption;
+import com.amap.api.location.AMapLocationListener;
+import com.amap.api.maps.AMap;
+import com.amap.api.maps.AMapOptions;
+import com.amap.api.maps.CameraUpdateFactory;
+import com.amap.api.maps.LocationSource;
+import com.amap.api.maps.MapView;
+import com.amap.api.maps.MapsInitializer;
+import com.amap.api.maps.model.BitmapDescriptor;
+import com.amap.api.maps.model.BitmapDescriptorFactory;
+import com.amap.api.maps.model.LatLng;
+import com.amap.api.maps.model.Marker;
+import com.amap.api.maps.model.MarkerOptions;
+import com.amap.api.maps.model.MyLocationStyle;
+import com.amap.api.maps.model.Poi;
+import com.amap.api.navi.AMapNavi;
+import com.amap.api.navi.AmapNaviPage;
+import com.amap.api.navi.AmapNaviParams;
+import com.amap.api.navi.AmapNaviType;
+import com.amap.api.navi.AmapPageType;
+import com.amap.api.navi.model.AMapCarInfo;
+import com.amap.api.navi.model.NaviPoi;
+import com.amap.api.services.core.AMapException;
+import com.amap.api.services.core.LatLonPoint;
+import com.amap.api.services.geocoder.GeocodeResult;
+import com.amap.api.services.geocoder.GeocodeSearch;
+import com.amap.api.services.geocoder.RegeocodeAddress;
+import com.amap.api.services.geocoder.RegeocodeQuery;
+import com.amap.api.services.geocoder.RegeocodeResult;
+import com.amap.api.services.help.Inputtips;
+import com.amap.api.services.help.InputtipsQuery;
+import com.amap.api.services.help.Tip;
+import com.amap.api.services.route.BusRouteResult;
+import com.amap.api.services.route.DrivePath;
+import com.amap.api.services.route.DriveRouteResult;
+import com.amap.api.services.route.RideRouteResult;
+import com.amap.api.services.route.RouteSearch;
+import com.amap.api.services.route.WalkRouteResult;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import androidx.annotation.NonNull;
+import io.flutter.plugin.platform.PlatformView;
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+/**
+ * 高德地图导航
+ */
+public class NativeMapView implements PlatformView, LocationSource, AMapLocationListener, GeocodeSearch.OnGeocodeSearchListener, RouteSearch.OnRouteSearchListener, AMap.OnMarkerClickListener, Inputtips.InputtipsListener {
+
+ private static final String TAG = "NativeMapView";
+ private final FrameLayout container;
+ private MapView mapView;
+ private AMap aMap;
+ private OnLocationChangedListener mListener;
+
+ private AMapLocationClient mlocationClient;
+ private final Context mContext;
+ private Activity mActivity;
+ private GeocodeSearch geocoderSearch;
+ private RouteSearch routeSearch;
+ private final OkHttpClient httpClient = new OkHttpClient();
+
+ // UI组件
+ private EditText endInput;
+ private LinearLayout searchArea; // 规划路线面板
+ private LinearLayout detailPanel; // 详情面板
+
+ private View modeMenu; //模式选择
+ private TextView tvStationName, tvStationAddr, planToggleBtn;
+ private ListView suggestionList; // 输入提示列表
+ private ArrayAdapter suggestionAdapter; // 提示列表适配器
+ private List currentTipList; // 当前提示列表
+ //时间 费用 里程 路费
+ private TextView tvDuration, tvDistance, tvTolls, tvTollsFuel;
+
+ private LatLng currentLatLng;
+ private String startName = "我的位置";
+ private String endName = "";
+ private String endAddress = "";
+ private LatLng startPoint;
+ private LatLng endPoint;
+ private boolean isFirstLocation = true;
+ private boolean isUserSelectedDestination = false; // 标识用户是否手动选择了目的地
+ private boolean isProgrammaticTextChange = false; // 标识是否是程序自动设置文本
+ private final List stationMarkers = new ArrayList<>();
+
+ // 存储token和车牌号
+ private String token;
+ private String plateNumber;
+
+ private String mDebugUrl = "https://beta-esg.api.lnh2e.com/appointment/";
+ private String mReleaseUrl = "";
+
+ // 存储货车路线算法接口返回的数据
+ private TruckRouteData truckRouteData;
+ //当前定位信息
+ private AMapLocation mLoc;
+
+
+ public NativeMapView(Context context, int id, Object args) {
+ this.mContext = context;
+ mActivity = getActivityFromContext(context);
+
+ MapsInitializer.updatePrivacyShow(mActivity, true, true);
+ MapsInitializer.updatePrivacyAgree(mActivity, true);
+
+ mapView = new MapView(context);
+ mapView.onCreate(null);
+ aMap = mapView.getMap();
+
+ container = new FrameLayout(context);
+ container.setClickable(true);
+ container.setFocusable(true);
+ container.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ container.addView(mapView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ initLoadingView(context);
+ initData(context);
+ initServices(context);
+ initOverlays(context);
+ setupMapUi();
+
+ if (context instanceof MainActivity) {
+ ((MainActivity) context).setMapView(this);
+ }
+ }
+
+
+ private void initServices(Context context) {
+ try {
+ geocoderSearch = new GeocodeSearch(context);
+ geocoderSearch.setOnGeocodeSearchListener(this);
+ routeSearch = new RouteSearch(context);
+ routeSearch.setRouteSearchListener(this);
+ } catch (AMapException e) {
+ Log.e(TAG, "服务初始化失败", e);
+ }
+ }
+
+ private LinearLayout bottomContainer;
+
+ private void initOverlays(Context context) {
+ // --- 底部主容器 (包含两个互斥显示的面板) ---
+ bottomContainer = new LinearLayout(context);
+ bottomContainer.setOrientation(LinearLayout.VERTICAL);
+ bottomContainer.setGravity(Gravity.BOTTOM);
+
+ // 1. 详情面板 (用于显示加氢站详细信息,初始隐藏)
+ detailPanel = createDetailPanel(context);
+ detailPanel.setVisibility(View.GONE);
+ bottomContainer.addView(detailPanel);
+
+ // 2. 搜索规划面板 (初始显示)
+ searchArea = new LinearLayout(context);
+ searchArea.setOrientation(LinearLayout.VERTICAL);
+ searchArea.setBackground(getRoundedDrawable(Color.WHITE, 16));
+ int p = dp2px(15);
+ searchArea.setPadding(p, p, p, p);
+ searchArea.setFocusable(true);
+ searchArea.setFocusableInTouchMode(true);
+
+ endInput = new EditText(context);
+ endInput.setHint("请输入目的地,不输入则自动匹配推荐加氢站");
+ endInput.setTextSize(13);
+ endInput.setHintTextColor(Color.GRAY);
+ endInput.setPadding(dp2px(12), dp2px(12), dp2px(12), dp2px(12));
+ endInput.setBackground(getRoundedDrawable(Color.parseColor("#F8F8F8"), 8));
+ endInput.setSingleLine(true);
+ endInput.setInputType(InputType.TYPE_CLASS_TEXT); // 明确输入类型
+ endInput.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
+ endInput.setFocusable(true);
+ endInput.setFocusableInTouchMode(true);
+ endInput.setClickable(true);
+ endInput.setCursorVisible(true);
+
+ // 针对 EditText 的特殊处理
+ endInput.setOnTouchListener((v, event) -> {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ v.requestFocus();
+ v.postDelayed(() -> {
+ InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ // 使用 SHOW_FORCED 或确保结果成功
+ imm.showSoftInput(v, InputMethodManager.SHOW_FORCED);
+ }
+ }, 100);
+ }
+ return false;
+ });
+
+ // 处理搜索键按下
+ endInput.setOnEditorActionListener((v, actionId, event) -> {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ suggestionList.setVisibility(View.GONE);
+ // 可以在这里直接触发路线规划,但保持原来的按钮点击逻辑
+ return true;
+ }
+ return false;
+ });
+
+ // 初始化提示列表
+ currentTipList = new ArrayList<>();
+ suggestionList = new ListView(context);
+ suggestionList.setBackgroundColor(Color.WHITE);
+ suggestionList.setDividerHeight(1);
+ suggestionList.setDivider(new ColorDrawable(Color.LTGRAY));
+ suggestionList.setPadding(dp2px(5), dp2px(5), dp2px(5), dp2px(5));
+ suggestionAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1);
+ suggestionList.setAdapter(suggestionAdapter);
+ suggestionList.setVisibility(View.GONE);
+ suggestionList.setOnItemClickListener((parent, view, position, id) -> {
+ Tip tip = currentTipList.get(position);
+ if (tip.getPoint() != null) {
+ endPoint = new LatLng(tip.getPoint().getLatitude(), tip.getPoint().getLongitude());
+ String name = tip.getName();
+ String district = tip.getDistrict();
+ endName = name;
+ isUserSelectedDestination = true; // 标识用户手动选择了目的地
+ isProgrammaticTextChange = true; // 标识是程序自动设置文本
+ endInput.setText(district != null && !district.isEmpty() ? name + " " + district : name);
+ isProgrammaticTextChange = false; // 恢复标志
+ suggestionList.setVisibility(View.GONE);
+ }
+ });
+
+ endInput.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) {
+ // 如果是程序自动设置文本,不触发搜索
+ if (isProgrammaticTextChange) {
+ return;
+ }
+
+ if (s.length() > 1) {
+ // 使用当前位置的城市进行搜索
+ String city = currentLatLng != null ? "" : "";
+ InputtipsQuery query = new InputtipsQuery(s.toString(), city);
+ query.setCityLimit(true);
+ Inputtips inputtips = new Inputtips(mContext, query);
+ inputtips.setInputtipsListener(NativeMapView.this);
+ inputtips.requestInputtipsAsyn();
+ } else {
+ suggestionList.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+ endInput.setOnFocusChangeListener((v, hasFocus) -> {
+ if (!hasFocus) {
+ suggestionList.setVisibility(View.GONE);
+ }
+ });
+
+ searchArea.addView(endInput);
+ searchArea.addView(suggestionList);
+
+ View vSpace = new View(context);
+ searchArea.addView(vSpace, new LinearLayout.LayoutParams(1, dp2px(12)));
+
+ Button planBtn = new Button(context);
+ planBtn.setText("规划路线");
+ planBtn.setTextColor(Color.WHITE);
+ planBtn.setTypeface(Typeface.DEFAULT_BOLD);
+ planBtn.setBackground(getRoundedDrawable(Color.parseColor("#017143"), 99));
+ planBtn.setOnClickListener(v -> calculateRouteBeforeNavi());
+ searchArea.addView(planBtn, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(48)));
+
+ bottomContainer.addView(searchArea);
+
+ // 设置统一的底部间距
+ FrameLayout.LayoutParams bottomParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ bottomParams.gravity = Gravity.BOTTOM;
+ bottomParams.setMargins(dp2px(12), 0, dp2px(12), dp2px(65));
+ container.addView(bottomContainer, bottomParams);
+
+
+ // --- 模式选择菜单 ---
+ modeMenu = createModeMenu(context);
+ modeMenu.setVisibility(View.GONE);
+ // 布局参数:位于规划按钮上方
+ FrameLayout.LayoutParams menuParams = new FrameLayout.LayoutParams(dp2px(130), ViewGroup.LayoutParams.WRAP_CONTENT);
+ menuParams.gravity = Gravity.BOTTOM | Gravity.END;
+ menuParams.setMargins(0, 0, dp2px(15), dp2px(330)); // 高度根据按钮位置调整
+ container.addView(modeMenu, menuParams);
+
+ // 加氢规划圆形按钮
+ planToggleBtn = new TextView(context);
+ planToggleBtn.setText("加氢\n规划");
+ planToggleBtn.setTextSize(11);
+ planToggleBtn.setTextColor(Color.WHITE);
+ planToggleBtn.setGravity(Gravity.CENTER);
+ planToggleBtn.setTypeface(Typeface.DEFAULT_BOLD);
+ // 设置深绿色圆形背景
+ planToggleBtn.setBackground(getCircleDrawable(Color.parseColor("#017143")));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ planToggleBtn.setElevation(dp2px(6));
+ }
+ planToggleBtn.setOnClickListener(v -> {
+ // 切换菜单显示/隐藏
+ modeMenu.setVisibility(modeMenu.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE);
+ });
+
+ int layoutSize = dp2px(44);
+ FrameLayout.LayoutParams toggleParams = new FrameLayout.LayoutParams(layoutSize, layoutSize);
+ toggleParams.gravity = Gravity.BOTTOM | Gravity.END;
+ toggleParams.setMargins(0, 0, dp2px(12), dp2px(340)); // 位于定位按钮上方
+ container.addView(planToggleBtn, toggleParams);
+
+ // --- 右下角定位按钮 ---
+ ImageButton locBtn = new ImageButton(context);
+ locBtn.setImageResource(R.drawable.ic_location);
+ // 设置自定义的白色圆形背景
+ locBtn.setBackgroundColor(Color.TRANSPARENT);
+ // 设置投影(仅在 API 21+ 有效,能产生干净的阴影而非黑块)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ locBtn.setElevation(dp2px(4));
+ }
+ locBtn.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ int padding = dp2px(2);
+ locBtn.setPadding(padding, padding, padding, padding);
+ locBtn.setOnClickListener(v -> {
+ if (currentLatLng != null)
+ aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 15f));
+ });
+ FrameLayout.LayoutParams locParams = new FrameLayout.LayoutParams(layoutSize, layoutSize);
+ locParams.setMargins(0, 0, dp2px(15), dp2px(285)); // 调高一点,避开底部的面板
+ locParams.gravity = Gravity.BOTTOM | Gravity.END;
+ container.addView(locBtn, locParams);
+
+ //最后调用监听函数
+ addKeyboardListener();
+ }
+
+ private void addKeyboardListener() {
+ container.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ Rect r = new Rect();
+ // 获取当前窗口可视区域
+ container.getWindowVisibleDisplayFrame(r);
+
+ // screenHeight - 可视区域高度 = 键盘高度
+ int screenHeight = container.getRootView().getHeight();
+ int keypadHeight = screenHeight - r.bottom;
+
+ // 如果键盘高度大于屏幕的 15%,说明键盘弹出了
+ if (keypadHeight > screenHeight * 0.15) {
+ // 键盘弹出:增加底部 Margin
+ updateBottomMargin(keypadHeight);
+ } else {
+ // 键盘收起:恢复原有 Margin (你代码中设置的是 dp2px(65))
+ updateBottomMargin(dp2px(65));
+ }
+ });
+ }
+
+ private void updateBottomMargin(int bottomPx) {
+ if (bottomContainer != null) {
+ FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) bottomContainer.getLayoutParams();
+ if (params != null) {
+ // 动态设置底部间距
+ params.setMargins(dp2px(12), 0, dp2px(12), bottomPx);
+ bottomContainer.setLayoutParams(params);
+ }
+ }
+ }
+
+ private LinearLayout createDetailPanel(Context context) {
+ LinearLayout panel = new LinearLayout(context);
+ panel.setOrientation(LinearLayout.VERTICAL);
+ panel.setBackground(getRoundedDrawable(Color.WHITE, 16));
+ panel.setPadding(dp2px(20), dp2px(20), dp2px(20), dp2px(20));
+
+ // --- (包含标题和关闭按钮) ---
+ LinearLayout titleLayout = new LinearLayout(context);
+ titleLayout.setOrientation(LinearLayout.HORIZONTAL);
+ titleLayout.setGravity(Gravity.CENTER_VERTICAL);
+
+ // 站点名称 (设置 weight: 1 占据剩余空间)
+ tvStationName = new TextView(context);
+ tvStationName.setTextSize(18);
+ tvStationName.setTextColor(Color.BLACK);
+ tvStationName.setTypeface(Typeface.DEFAULT_BOLD);
+ LinearLayout.LayoutParams nameParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f);
+ titleLayout.addView(tvStationName, nameParams);
+
+ // 关闭按钮 (X)
+ ImageView ivClose = new ImageView(context);
+ ivClose.setImageResource(R.drawable.ic_close);
+ ivClose.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ ivClose.setPadding(dp2px(5), dp2px(5), dp2px(5), dp2px(5));
+ ivClose.setOnClickListener(v -> {
+ if (detailPanel != null)
+ detailPanel.setVisibility(View.GONE);
+ searchArea.setVisibility(View.VISIBLE);
+ planToggleBtn.setVisibility(View.VISIBLE);
+ });
+
+ LinearLayout.LayoutParams closeParams = new LinearLayout.LayoutParams(dp2px(28), dp2px(28));
+ titleLayout.addView(ivClose, closeParams);
+
+ panel.addView(titleLayout);
+
+ tvStationAddr = new TextView(context);
+ tvStationAddr.setTextSize(13);
+ tvStationAddr.setPadding(0, dp2px(4), 0, 0);
+ tvStationAddr.setTextColor(Color.GRAY);
+ panel.addView(tvStationAddr);
+
+ /*内容*/
+ LinearLayout routeInfoLayout = new LinearLayout(context);
+ routeInfoLayout.setOrientation(LinearLayout.VERTICAL);
+ routeInfoLayout.setPadding(0, dp2px(5), 0, 0);
+
+ // 第一行:预计时间
+ LinearLayout row1 = new LinearLayout(context);
+ row1.setOrientation(LinearLayout.HORIZONTAL);
+ row1.setGravity(Gravity.CENTER_VERTICAL);
+ row1.setPadding(0, dp2px(8), 0, 0); // 增加行间距
+ tvDuration = createInfoItem(row1, R.drawable.ic_time, "预计时间:", "", 1.0f);
+ tvTollsFuel = createInfoItem(row1, R.drawable.ic_fuel, "加氢费用:", "", 1.0f);
+ routeInfoLayout.addView(row1);
+
+ // 第二行:里程 + 过路费
+ LinearLayout row2 = new LinearLayout(context);
+ row2.setOrientation(LinearLayout.HORIZONTAL);
+ row2.setGravity(Gravity.CENTER_VERTICAL);
+ row2.setPadding(0, dp2px(10), 0, 0); // 增加行间距
+
+ // 里程
+ tvDistance = createInfoItem(row2, R.drawable.ic_mileage, "行驶里程:", "", 1.0f);
+ // 过路费
+ tvTolls = createInfoItem(row2, R.drawable.ic_toll, "过路费:", "", 1.0f);
+
+ routeInfoLayout.addView(row2);
+ panel.addView(routeInfoLayout);
+
+ Button startNaviBtn = new Button(context);
+ startNaviBtn.setText("开始导航");
+ startNaviBtn.setTextColor(Color.WHITE);
+ startNaviBtn.setBackground(getRoundedDrawable(Color.parseColor("#017143"), 10));
+ startNaviBtn.setOnClickListener(v -> startRouteSearch());
+
+ LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp2px(48));
+ btnParams.topMargin = dp2px(15);
+ panel.addView(startNaviBtn, btnParams);
+
+ return panel;
+ }
+
+ private void calculateRouteBeforeNavi() {
+ if (startPoint == null) {
+ Toast.makeText(mContext, "正在定位...", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (endPoint == null) {
+ Toast.makeText(mContext, "请先选择目的地", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ fetchTruckRouteAlgorithm(mLoc);
+ }
+
+ @Override
+ public void onGetInputtips(List tipList, int rCode) {
+ if (rCode == 1000 && tipList != null && !tipList.isEmpty()) {
+ currentTipList.clear();
+ currentTipList.addAll(tipList);
+
+ List 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);
+ }
+
+ suggestionAdapter.clear();
+ suggestionAdapter.addAll(suggestionNames);
+ suggestionAdapter.notifyDataSetChanged();
+
+ // 显示提示列表
+ new Handler(Looper.getMainLooper()).post(() -> {
+ if (suggestionNames.size() > 0) {
+ suggestionList.setVisibility(View.VISIBLE);
+ // 限制显示的高度
+ int height = Math.min(suggestionNames.size() * dp2px(45), dp2px(200));
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ height
+ );
+ suggestionList.setLayoutParams(params);
+ } else {
+ suggestionList.setVisibility(View.GONE);
+ }
+ });
+ } else {
+ // 请求失败或无结果,隐藏提示列表
+ new Handler(Looper.getMainLooper()).post(() -> suggestionList.setVisibility(View.GONE));
+ }
+ }
+
+ @Override
+ public void onDriveRouteSearched(DriveRouteResult result, int rCode) {
+ if (rCode == AMapException.CODE_AMAP_SUCCESS && result != null && !result.getPaths().isEmpty()) {
+ DrivePath path = result.getPaths().get(0);
+
+ // 规划成功,显示详情面板,隐藏模式选择
+ detailPanel.setVisibility(View.VISIBLE);
+ planToggleBtn.setVisibility(View.GONE);
+ modeMenu.setVisibility(View.GONE);
+
+ tvStationName.setText(endName);
+ tvStationAddr.setText(endAddress);
+
+
+ double distanceKm = path.getDistance() / 1000f;
+ long durationMin = path.getDuration() / 60;
+ double tolls = path.getTolls();
+ String hydrogenCost = truckRouteData.algorithmPath.hydrogenCost;
+
+ tvDuration.setText("预计时间:" + durationMin + "分钟");
+ tvDistance.setText("行驶里程:" + String.format("%.1f", distanceKm) + "公里");
+ tvTolls.setText("过路费:" + (int) tolls + "元");
+ tvTollsFuel.setText("加氢费用:" + hydrogenCost + "元");
+
+ aMap.animateCamera(CameraUpdateFactory.newLatLngZoom(endPoint, 13f));
+ } else {
+ // 规划失败回退面板
+ searchArea.setVisibility(View.VISIBLE);
+ Toast.makeText(mContext, "路径规划失败: " + rCode, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void startRouteSearch() {
+ if (mActivity == null || startPoint == null) {
+ Toast.makeText(mContext, "定位信息不足,请稍后再试", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ //确定目的地坐标和名称
+ LatLng finalDestinationLatLng = null;
+ String finalDestinationName = endName;
+
+ // 优先级:用户手动选择 > truckRouteData (接口返回的精准点) > endPoint (地图点击/搜索的点)
+ if (isUserSelectedDestination && endPoint != null) {
+ finalDestinationLatLng = endPoint;
+ finalDestinationName = endName;
+ } else if (truckRouteData != null && truckRouteData.destinationSite != null) {
+ // 否则使用接口返回的推荐加氢站地址
+ String latStr = truckRouteData.destinationSite.latitude;
+ String lngStr = truckRouteData.destinationSite.longitude;
+
+ //防止 parseDouble 崩溃
+ if (latStr != null && !latStr.isEmpty() && lngStr != null && !lngStr.isEmpty()) {
+ try {
+ finalDestinationLatLng = new LatLng(Double.parseDouble(latStr),
+ Double.parseDouble(lngStr));
+ if (truckRouteData.destinationSite.name != null &&
+ !truckRouteData.destinationSite.name.isEmpty()) {
+ finalDestinationName = truckRouteData.destinationSite.name;
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "解析 truckRouteData 坐标失败: " + e.getMessage());
+ }
+ }
+ }
+
+ // 如果接口数据不可用,使用之前标记点/输入点作为兜底
+ if (finalDestinationLatLng == null) {
+ finalDestinationLatLng = endPoint;
+ }
+
+ // 最终校验
+ if (finalDestinationLatLng == null) {
+ Toast.makeText(mContext, "请先选择有效的目的地", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ //获取途径点
+ List waysPoiIds = new ArrayList<>();
+ if (truckRouteData != null && truckRouteData.pathDto != null && truckRouteData.pathDto.naviList !=
+ null) {
+ for (NaviPoint np : truckRouteData.pathDto.naviList) {
+ if (np.coordinate != null && !TextUtils.isEmpty(np.coordinate.latitude) &&
+ !TextUtils.isEmpty(np.coordinate.longitude)) {
+ try {
+ double lat = Double.parseDouble(np.coordinate.latitude);
+ double lng = Double.parseDouble(np.coordinate.longitude);
+ waysPoiIds.add(new NaviPoi(np.name, new LatLng(lat, lng), np.poiId));
+ } catch (Exception e) {
+ Log.e(TAG, "途径点坐标解析错误: " + np.name);
+ }
+ }
+ }
+ }
+ //车辆信息
+ AMapCarInfo carInfo = new AMapCarInfo();
+ carInfo.setCarNumber(plateNumber);
+ carInfo.setCarType(String.valueOf(truckRouteData.truckDto.mcarType));
+ carInfo.setVehicleAxis(String.valueOf(truckRouteData.truckDto.mvehicleAxis));
+ carInfo.setVehicleHeight(truckRouteData.truckDto.mvehicleHeight);
+ carInfo.setVehicleLength(truckRouteData.truckDto.mvehicleLength);
+ carInfo.setVehicleWidth(truckRouteData.truckDto.mvehicleWidth);
+ carInfo.setVehicleSize(String.valueOf(truckRouteData.truckDto.mvehicleSize));
+ carInfo.setVehicleLoad(truckRouteData.truckDto.mvehicleLoad);
+ carInfo.setVehicleWeight(truckRouteData.truckDto.mvehicleWeight);
+ carInfo.setRestriction(truckRouteData.truckDto.isRestriction);
+ carInfo.setVehicleLoadSwitch(truckRouteData.truckDto.mvehicleLoadSwitch);
+
+ // 检查途径点数量,决定使用哪种导航方式
+ int wayPointsCount = waysPoiIds.size();
+ Log.d(TAG, "途经点数量: " + wayPointsCount);
+ if (wayPointsCount > 3) {
+ // 途经点超过3个,跳转到 NavigationActivity
+ Intent intent = new Intent(mContext, NavigationActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // 设置起点和终点 POI
+ NaviPoi startNaviPoi = new NaviPoi("", startPoint, "");
+ NaviPoi endNaviPoi = new NaviPoi("", finalDestinationLatLng, "");
+ intent.putExtra("startPoi", startNaviPoi);
+ intent.putExtra("endPoi", endNaviPoi);
+
+ // 设置车辆信息
+ if (truckRouteData != null && truckRouteData.truckDto != null) {
+ intent.putExtra("carInfo", carInfo);
+ }
+
+ // 设置途径点
+ if (!waysPoiIds.isEmpty()) {// 必须转换为 ArrayList 才能调用 putParcelableArrayListExtra
+ ArrayList wayPointsArrayList = new ArrayList<>(waysPoiIds);
+ intent.putParcelableArrayListExtra("wayPoints", wayPointsArrayList);
+ }
+
+ mContext.startActivity(intent);
+
+ } else {
+ Poi start = new Poi(startName, startPoint, "");
+ Poi end = new Poi(finalDestinationName, finalDestinationLatLng, "");
+ AmapNaviParams params = new AmapNaviParams(start, null, end, AmapNaviType.DRIVER,
+ AmapPageType.ROUTE);
+
+ try {
+ AMapNavi mAMapNavi = AMapNavi.getInstance(mContext);
+ // 设置车辆信息
+ if (truckRouteData != null && truckRouteData.truckDto != null) {
+ params.setCarInfo(carInfo);
+ mAMapNavi.setCarInfo(carInfo);
+ }
+ params.setNeedDestroyDriveManagerInstanceWhenNaviExit(true);
+ AmapNaviPage.getInstance().showRouteActivity(mActivity, params, null);
+ } catch (Exception e) {
+ Log.e(TAG, "配置导航参数出错: " + e.getMessage());
+ }
+ }
+ }
+
+ private void initData(Context context) {
+ //获取从Flutter端存储的车牌和token
+ android.content.SharedPreferences prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
+ String plateNumber = prefs.getString("flutter.plateNumber", "");
+ String token = prefs.getString("flutter.token", "");
+
+ Log.d(TAG, "initData - plateNumber: " + plateNumber + ", token: " + (token.isEmpty() ? "empty" : "present"));
+
+ // 保存token供后续使用
+ this.token = token;
+ this.plateNumber = plateNumber;
+ }
+
+ // 当前选中的站点ID,默认id和选择地图id
+ private String selectedSiteId = "";
+
+ /**
+ * 定位成功后调用,地图选点后调用
+ * 获取货车路线算法信息
+ */
+ private void fetchTruckRouteAlgorithm(AMapLocation loc) {
+
+ showLoading();
+
+ if (plateNumber == null || plateNumber.isEmpty()) {
+ return;
+ }
+
+ try {
+ JSONObject json = new JSONObject();
+ json.put("longitude", String.valueOf(loc.getLongitude()));
+ json.put("latitude", String.valueOf(loc.getLatitude()));
+ json.put("plateNumber", plateNumber);
+ json.put("hydrogenSiteId", selectedSiteId);
+
+ Request.Builder requestBuilder = new Request.Builder().url(mDebugUrl + "truck/truckRouteAlgorithm").post(RequestBody.create(json.toString(), MediaType.parse("application/json")));
+
+ if (token != null && !token.isEmpty()) {
+ requestBuilder.addHeader("asoco-token", token);
+ }
+ Log.d("566-", "asoco-token:" + token + "requestBuilder:" + json);
+ httpClient.newCall(requestBuilder.build()).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ Log.e(TAG, "fetchTruckRouteAlgorithm failed", e);
+ dismissLoading();
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ if (response.isSuccessful() && response.body() != null) {
+ try {
+ String responseString = response.body().string();
+ Log.d(TAG, "fetchTruckRouteAlgorithm response: " + responseString);
+
+ JSONObject res = new JSONObject(responseString);
+ if (res.getInt("code") == 200) {
+ JSONObject data = res.getJSONObject("data");
+ truckRouteData = parseTruckRouteData(data);
+ Log.d(TAG, "TruckRouteAlgorithm data loaded");
+
+ dismissLoading();
+
+ // 开始规划前隐藏输入框面板
+ searchArea.setVisibility(View.GONE);
+
+ RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(new LatLonPoint(startPoint.latitude, startPoint.longitude), new LatLonPoint(endPoint.latitude, endPoint.longitude));
+ RouteSearch.DriveRouteQuery query = new RouteSearch.DriveRouteQuery(fromAndTo, RouteSearch.DRIVING_SINGLE_DEFAULT, null, null, "");
+ routeSearch.calculateDriveRouteAsyn(query);
+ } else {
+ dismissLoading();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "parseTruckRouteAlgorithm error", e);
+ }
+ } else {
+ dismissLoading();
+ }
+ }
+ });
+ } catch (Exception e) {
+ Log.e(TAG, "fetchTruckRouteAlgorithm error", e);
+ dismissLoading();
+ }
+ }
+
+ private ProgressBar progressBar;
+ private FrameLayout loadingOverlay;
+
+ private void initLoadingView(Context context) {
+ // 创建遮罩层 (全屏透明,拦截点击)
+ loadingOverlay = new FrameLayout(context);
+ loadingOverlay.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ loadingOverlay.setClickable(true); // 拦截点击
+ loadingOverlay.setFocusable(true);
+ loadingOverlay.setVisibility(View.GONE); // 默认隐藏
+
+ progressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleLarge);
+ FrameLayout.LayoutParams progressParams = new FrameLayout.LayoutParams(
+ dp2px(45), dp2px(45));
+ progressParams.gravity = Gravity.CENTER; // 居中显示
+
+ // 把进度条加到遮罩层
+ loadingOverlay.addView(progressBar, progressParams);
+
+ // 最后把整个遮罩层添加到主容器的最顶层
+ container.addView(loadingOverlay);
+ }
+
+ private void showLoading() {
+ if (mActivity == null)
+ return;
+ mActivity.runOnUiThread(() -> {
+ if (loadingOverlay != null) {
+ loadingOverlay.setVisibility(View.VISIBLE);
+ }
+ // 如果想在加载时给用户文字提示,可以动态加一个 TextView 在 ProgressBar 下方
+ });
+ }
+
+ private void dismissLoading() {
+ if (mActivity == null)
+ return;
+ mActivity.runOnUiThread(() -> {
+ if (loadingOverlay != null) {
+ loadingOverlay.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ private void setupMapUi() {
+ aMap.setLocationSource(this);
+ aMap.setMyLocationEnabled(true);
+ aMap.setOnMarkerClickListener(this);
+
+ // 添加地图触摸监听器,点击地图时隐藏提示列表
+ aMap.setOnMapClickListener(latLng -> {
+ if (suggestionList != null && suggestionList.getVisibility() == View.VISIBLE) {
+ suggestionList.setVisibility(View.GONE);
+ }
+ });
+
+ MyLocationStyle myLocationStyle = new MyLocationStyle();
+ try {
+ Bitmap carBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.car);
+ if (carBitmap != null) {
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(carBitmap, dp2px(30), dp2px(30), true);
+ myLocationStyle.myLocationIcon(BitmapDescriptorFactory.fromBitmap(scaledBitmap));
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ myLocationStyle.anchor(0.5f, 0.5f);
+ myLocationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE_NO_CENTER);
+ aMap.setMyLocationStyle(myLocationStyle);
+ aMap.getUiSettings().setZoomControlsEnabled(false);
+ aMap.getUiSettings().setLogoPosition(AMapOptions.LOGO_POSITION_BOTTOM_LEFT);
+ }
+
+ private GradientDrawable getRoundedDrawable(int color, int radiusDp) {
+ GradientDrawable shape = new GradientDrawable();
+ shape.setShape(GradientDrawable.RECTANGLE);
+ shape.setCornerRadius(dp2px(radiusDp));
+ shape.setColor(color);
+ return shape;
+ }
+
+ private int dp2px(float dp) {
+ return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mContext.getResources().getDisplayMetrics());
+ }
+
+ @Override
+ public void activate(OnLocationChangedListener listener) {
+ mListener = listener;
+ startLocation();
+ }
+
+ @Override
+ public void deactivate() {
+ mListener = null;
+ if (mlocationClient != null)
+ mlocationClient.stopLocation();
+ }
+
+ public void startLocation() {
+ if (mlocationClient == null) {
+ try {
+ mlocationClient = new AMapLocationClient(mContext);
+ AMapLocationClientOption option = new AMapLocationClientOption();
+ mlocationClient.setLocationListener(this);
+ option.setNeedAddress(true);
+ option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy);
+ mlocationClient.setLocationOption(option);
+ mlocationClient.startLocation();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void onLocationChanged(AMapLocation loc) {
+ mLoc = loc;
+ if (mLoc != null && mLoc.getErrorCode() == 0) {
+ currentLatLng = new LatLng(mLoc.getLatitude(), mLoc.getLongitude());
+ if (mListener != null)
+ mListener.onLocationChanged(mLoc);
+ if (isFirstLocation) {
+ isFirstLocation = false;
+ startPoint = currentLatLng;
+ aMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 14f));
+ getAddressByLatlng(currentLatLng);
+ fetchRecommendStation(mLoc);
+ fetchNearbyStations(mLoc);
+
+ }
+ }
+ }
+
+ private void getAddressByLatlng(LatLng latLng) {
+ geocoderSearch.getFromLocationAsyn(new RegeocodeQuery(new LatLonPoint(latLng.latitude, latLng.longitude), 200, GeocodeSearch.AMAP));
+ }
+
+ @Override
+ public void onRegeocodeSearched(RegeocodeResult result, int rCode) {
+ if (rCode == 1000 && result != null) {
+ RegeocodeAddress addr = result.getRegeocodeAddress();
+ startName = addr.getStreetNumber() != null ? addr.getStreetNumber().getStreet() : addr.getDistrict();
+ }
+ }
+
+ /**
+ * 获取定位地区的推荐站点
+ *
+ * @param loc
+ */
+ private void fetchRecommendStation(AMapLocation loc) {
+ try {
+ JSONObject json = new JSONObject();
+ json.put("province", loc.getProvince());
+ json.put("city", loc.getCity().isEmpty() ? loc.getProvince() : loc.getCity());
+ json.put("longitude", loc.getLongitude());
+ json.put("latitude", loc.getLatitude());
+
+ httpClient.newCall(new Request.Builder().url(mDebugUrl + "station/getStationInfoByArea").post(RequestBody.create(json.toString(), MediaType.parse("application/json"))).build()).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ if (response.isSuccessful() && response.body() != null) {
+ try {
+ JSONObject res = new JSONObject(response.body().string());
+ if (res.getInt("code") == 0 && !res.isNull("data")) {
+ JSONObject data = res.getJSONObject("data");
+ endPoint = new LatLng(data.getDouble("latitude"), data.getDouble("longitude"));
+ endName = data.getString("name");
+ selectedSiteId = data.optString("id", "");
+ endAddress = data.optString("address", "");
+ isUserSelectedDestination = false; // 系统推荐的地址,不是用户选择
+ new Handler(Looper.getMainLooper()).post(() -> {
+ markStation(endPoint, endName, endAddress, selectedSiteId, true);
+ });
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void fetchNearbyStations(AMapLocation loc) {
+ try {
+ JSONObject json = new JSONObject();
+ json.put("longitude", loc.getLongitude());
+ json.put("latitude", loc.getLatitude());
+
+ httpClient.newCall(new Request.Builder().url(mDebugUrl + "station/getNearbyHydrogenStationsByLocation").post(RequestBody.create(json.toString(), MediaType.parse("application/json"))).build()).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ if (response.isSuccessful() && response.body() != null) {
+ try {
+ JSONObject res = new JSONObject(response.body().string());
+ JSONArray array = res.getJSONArray("data");
+ new Handler(Looper.getMainLooper()).post(() -> {
+ for (int i = 0; i < array.length(); i++) {
+ try {
+ JSONObject item = array.getJSONObject(i);
+ String id = item.getString("id");
+ //推荐站点跳出
+ if (selectedSiteId.equals(id)) {
+ continue;
+ }
+ markStation(new LatLng(item.getDouble("latitude"), item.getDouble("longitude")), item.getString("name"), item.getString("address"), id, false);
+ } catch (Exception ignored) {
+ }
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ private void markStation(LatLng latLng, String name, String address, String stationId, boolean isRecommend) {
+
+ String displayName = name;
+ if (displayName != null && displayName.length() > 7) {
+ displayName = displayName.substring(0, 7) + "...";
+ }
+
+ BitmapDescriptor icon = getMarkerIconWithText(mContext, displayName, isRecommend);
+
+ MarkerOptions markerOptions = new MarkerOptions()
+ .title(name).
+ position(latLng).icon(icon).anchor(0.5f, 0.8f);
+
+ Marker m = aMap.addMarker(markerOptions);
+
+ Map dataMap = new HashMap<>();
+ dataMap.put("latLng", latLng);
+ dataMap.put("stationId", stationId);
+ dataMap.put("address", address);
+ m.setObject(dataMap);
+
+ stationMarkers.add(m);
+ }
+
+ @Override
+ public boolean onMarkerClick(Marker marker) {
+
+ //地图选点
+ for (Marker m : stationMarkers) {
+ m.setIcon(BitmapDescriptorFactory.fromResource(m.equals(marker) ? R.drawable.ic_marker : R.drawable.ic_un_marker));
+ }
+
+
+ Object obj = marker.getObject();
+ if (obj instanceof Map) {
+ Map dataMap = (Map) obj;
+
+ // 获取坐标并赋值
+ LatLng latLng = (LatLng) dataMap.get("latLng");
+ if (latLng != null) {
+ this.endPoint = latLng;
+ }
+
+ // 获取站点ID并赋值
+ String stationId = (String) dataMap.get("stationId");
+ String address = (String) dataMap.get("address");
+ if (stationId != null) {
+ this.selectedSiteId = stationId;
+ this.endAddress = address;
+ }
+
+ // 更新 UI 和 业务逻辑
+ endName = marker.getTitle();
+ isUserSelectedDestination = true; // 标识用户手动选择了目的地
+
+ // 需要传入当前位置以便接口计算路线
+ if (mlocationClient != null && mlocationClient.getLastKnownLocation() != null) {
+ fetchTruckRouteAlgorithm(mlocationClient.getLastKnownLocation());
+ } else if (currentLatLng != null) {
+ // 如果没有精准定位,尝试使用上次记录的经纬度
+ AMapLocation tempLoc = new AMapLocation("");
+ tempLoc.setLatitude(currentLatLng.latitude);
+ tempLoc.setLongitude(currentLatLng.longitude);
+ fetchTruckRouteAlgorithm(tempLoc);
+ }
+
+
+ // 计算路径
+ calculateRouteBeforeNavi();
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onGeocodeSearched(GeocodeResult r, int c) {
+ }
+
+ @Override
+ public void onBusRouteSearched(BusRouteResult r, int c) {
+ }
+
+ @Override
+ public void onWalkRouteSearched(WalkRouteResult r, int c) {
+ }
+
+ @Override
+ public void onRideRouteSearched(RideRouteResult r, int c) {
+ }
+
+ private Activity getActivityFromContext(Context context) {
+ if (context instanceof Activity)
+ return (Activity) context;
+ if (context instanceof android.content.ContextWrapper)
+ return getActivityFromContext(((android.content.ContextWrapper) context).getBaseContext());
+ return null;
+ }
+
+ public void onResume() {
+ mapView.onResume();
+ }
+
+ public void onPause() {
+ mapView.onPause();
+ }
+
+ public void onSaveInstanceState(Bundle out) {
+ mapView.onSaveInstanceState(out);
+ }
+
+ @Override
+ public View getView() {
+ return container;
+ }
+
+ @Override
+ public void dispose() {
+ if (mlocationClient != null) {
+ mlocationClient.stopLocation();
+ mlocationClient.onDestroy();
+ }
+ mapView.onDestroy();
+ }
+
+ public BitmapDescriptor getMarkerIconWithText(Context context, String text, boolean isRecommend) {
+ // 创建主容器
+ LinearLayout container = new LinearLayout(context);
+ container.setOrientation(LinearLayout.VERTICAL);
+ container.setGravity(Gravity.CENTER_HORIZONTAL);
+ // 必须要给容器本身也设置一个基础参数,否则测量可能失败
+ container.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
+
+ // 文字部分:必须显式设置 LayoutParams
+ TextView textView = new TextView(context);
+ textView.setText(text);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
+ textView.setTextColor(Color.BLACK);
+ textView.setTypeface(null, Typeface.BOLD);
+ // 设置文字的参数为 WRAP_CONTENT
+ LinearLayout.LayoutParams textLp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+ textView.setLayoutParams(textLp);
+ container.addView(textView);
+
+ // 图标部分:设置固定大小
+ ImageView imageView = new ImageView(context);
+ imageView.setImageResource(isRecommend ? R.drawable.ic_marker : R.drawable.ic_un_marker);
+
+ int size = dp2px(30);
+ LinearLayout.LayoutParams imgLp = new LinearLayout.LayoutParams(size, size);
+ imgLp.topMargin = dp2px(2);
+ imageView.setLayoutParams(imgLp);
+ imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ container.addView(imageView);
+
+ // --- 精准测量 ---
+ int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ container.measure(spec, spec);
+
+ // 获取测量后的实际宽高
+ int measuredWidth = container.getMeasuredWidth();
+ int measuredHeight = container.getMeasuredHeight();
+
+ // 如果测出来是 0,说明有问题
+ if (measuredWidth <= 0 || measuredHeight <= 0) {
+ // 兜底逻辑:如果测量失败,给一个默认大小
+ measuredWidth = dp2px(100);
+ measuredHeight = dp2px(60);
+ }
+
+ container.layout(0, 0, measuredWidth, measuredHeight);
+
+ // 绘制到 Bitmap
+ Bitmap bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ // 绘制前可以选清空背景(透明)
+ canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
+ container.draw(canvas);
+
+ return BitmapDescriptorFactory.fromBitmap(bitmap);
+ }
+
+ /**
+ * 辅助方法:创建带图标的文本项
+ */
+ private TextView createInfoItem(LinearLayout parent, int iconRes, String label, String value, float weight) {
+ LinearLayout itemContainer = new LinearLayout(mContext);
+ itemContainer.setOrientation(LinearLayout.HORIZONTAL);
+ itemContainer.setGravity(Gravity.CENTER_VERTICAL);
+
+ // 图标
+ ImageView iv = new ImageView(mContext);
+ iv.setImageResource(iconRes);
+ iv.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ iv.setColorFilter(Color.parseColor("#017143")); // 统一设为主题绿
+ itemContainer.addView(iv, new LinearLayout.LayoutParams(dp2px(18), dp2px(18)));
+
+ // 文本内容
+ TextView tv = new TextView(mContext);
+ tv.setText(label + value);
+ tv.setTextSize(14);
+ tv.setTextColor(Color.parseColor("#333333"));
+ tv.setPadding(dp2px(6), 0, 0, 0);
+
+ LinearLayout.LayoutParams tvParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1.0f);
+ itemContainer.addView(tv, tvParams);
+
+ // 将整个项加入父容器
+ LinearLayout.LayoutParams containerParams = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, weight);
+ parent.addView(itemContainer, containerParams);
+
+ return tv; // 返回TextView以便后续更新内容
+ }
+
+ // 过载一个不需要weight的方法给第一行使用
+ private TextView createInfoItem(LinearLayout parent, int iconRes, String label, String value) {
+ LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ ImageView iv = new ImageView(mContext);
+ iv.setImageResource(iconRes);
+ iv.setColorFilter(Color.parseColor("#017143"));
+ parent.addView(iv, new LinearLayout.LayoutParams(dp2px(18), dp2px(18)));
+
+ TextView tv = new TextView(mContext);
+ tv.setTextSize(14);
+ tv.setTextColor(Color.parseColor("#333333"));
+ tv.setPadding(dp2px(6), 0, 0, 0);
+ parent.addView(tv, params);
+
+ return tv;
+ }
+
+ // 创建模式菜单视图
+ private View createModeMenu(Context context) {
+ LinearLayout menu = new LinearLayout(context);
+ menu.setOrientation(LinearLayout.VERTICAL);
+ menu.setBackground(getRoundedDrawable(Color.WHITE, 12));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ menu.setElevation(dp2px(10));
+ }
+
+ // 1. 成本计算模式 (高亮绿色头部)
+ TextView item1 = createMenuItem(context, "成本计算模式", false);
+ item1.setOnClickListener(v -> switchMode("成本计算"));
+
+ // 2. 送货规划模式
+ TextView item2 = createMenuItem(context, "送货规划模式", false);
+ item2.setOnClickListener(v -> switchMode("送货规划"));
+
+ // 3. 加氢规划模式
+ TextView item3 = createMenuItem(context, "加氢规划模式", true);
+ item3.setOnClickListener(v -> switchMode("加氢规划"));
+
+ menu.addView(item1);
+ menu.addView(item2);
+ menu.addView(item3);
+ return menu;
+ }
+
+ private TextView createMenuItem(Context context, String text, boolean isHighlight) {
+ TextView tv = new TextView(context);
+ tv.setText(text);
+ tv.setTextSize(13);
+ tv.setPadding(dp2px(15), dp2px(12), dp2px(15), dp2px(12));
+ tv.setGravity(Gravity.CENTER);
+ if (isHighlight) {
+ tv.setTextColor(Color.WHITE);
+ // 顶部圆角绿色背景
+ tv.setBackground(getTopRoundedDrawable(Color.parseColor("#27AE60"), 12));
+ } else {
+ tv.setTextColor(Color.parseColor("#666666"));
+ tv.setBackgroundColor(Color.TRANSPARENT);
+ }
+ return tv;
+ }
+
+ private void switchMode(String mode) {
+ // 处理模式切换逻辑
+ modeMenu.setVisibility(View.GONE);
+ }
+
+ // 获取纯圆形背景
+ private GradientDrawable getCircleDrawable(int color) {
+ GradientDrawable gd = new GradientDrawable();
+ gd.setColor(color);
+ gd.setShape(GradientDrawable.OVAL);
+ return gd;
+ }
+
+ // 获取顶部带圆角,底部直角的背景(用于菜单第一项)
+ private GradientDrawable getTopRoundedDrawable(int color, int radiusDp) {
+ float r = dp2px(radiusDp);
+ GradientDrawable gd = new GradientDrawable();
+ gd.setColor(color);
+ // 顺序:左上x2, 右上x2, 右下x2, 左下x2
+ gd.setCornerRadii(new float[]{r, r, r, r, r, r, r, r});
+ return gd;
+ }
+
+
+ /**
+ * 解析接口返回的data数据
+ */
+ private TruckRouteData parseTruckRouteData(JSONObject data) throws Exception {
+ TruckRouteData truckRouteData = new TruckRouteData();
+
+ // 解析truckDto
+ if (data.has("truckDto") && !data.isNull("truckDto")) {
+ JSONObject truckJson = data.getJSONObject("truckDto");
+ TruckDto truckDto = new TruckDto();
+ truckDto.isRestriction = truckJson.optBoolean("isRestriction", true);
+ truckDto.mvehicleSizeName = truckJson.optString("mvehicleSizeName", "");
+ truckDto.mvehicleAxisUnit = truckJson.optString("mvehicleAxisUnit", "轴");
+ truckDto.mcarNumber = truckJson.optString("mcarNumber", "");
+ truckDto.mcarType = truckJson.optInt("mcarType", 0);
+ truckDto.mvehicleHeight = truckJson.optString("mvehicleHeight", "");
+ truckDto.mvehicleHeightUnit = truckJson.optString("mvehicleHeightUnit", "M");
+ truckDto.mvehicleWeight = truckJson.optString("mvehicleWeight", "");
+ truckDto.mvehicleWeightUnit = truckJson.optString("mvehicleWeightUnit", "T");
+ truckDto.mvehicleLoad = truckJson.optString("mvehicleLoad", "");
+ truckDto.mvehicleLoadUnit = truckJson.optString("mvehicleLoadUnit", "T");
+ truckDto.mvehicleLoadSwitch = truckJson.optBoolean("mvehicleLoadSwitch", false);
+ truckDto.mvehicleWidth = truckJson.optString("mvehicleWidth", "");
+ truckDto.mvehicleWidthUnit = truckJson.optString("mvehicleWidthUnit", "M");
+ truckDto.mvehicleLength = truckJson.optString("mvehicleLength", "");
+ truckDto.mvehicleLengthUnit = truckJson.optString("mvehicleLengthUnit", "M");
+ truckDto.mvehicleSize = truckJson.optInt("mvehicleSize", 2);
+ truckDto.mvehicleAxis = truckJson.optInt("mvehicleAxis", 2);
+ truckRouteData.truckDto = truckDto;
+ }
+
+ // 解析destinationSite
+ if (data.has("destinationSite") && !data.isNull("destinationSite")) {
+ JSONObject siteJson = data.getJSONObject("destinationSite");
+ DestinationSite destinationSite = new DestinationSite();
+ destinationSite.innerSiteId = siteJson.optString("innerSiteId", "");
+ destinationSite.name = siteJson.optString("name", "");
+ destinationSite.shortName = siteJson.optString("shortName", "");
+ destinationSite.siteNo = siteJson.optString("siteNo", "");
+ destinationSite.city = siteJson.optString("city", "");
+ destinationSite.address = siteJson.optString("address", "");
+ destinationSite.contact = siteJson.optString("contact", "");
+ destinationSite.phone = siteJson.optString("phone", "");
+ destinationSite.type = siteJson.optString("type", "");
+ destinationSite.coOpMode = siteJson.optString("coOpMode", "");
+ destinationSite.booking = siteJson.optString("booking", "");
+ destinationSite.siteStatus = siteJson.optString("siteStatus", "");
+ destinationSite.siteStatusName = siteJson.optString("siteStatusName", "");
+ destinationSite.startBusiness = siteJson.optString("startBusiness", "");
+ destinationSite.endBusiness = siteJson.optString("endBusiness", "");
+ destinationSite.billingMethod = siteJson.optString("billingMethod", "");
+ destinationSite.term = siteJson.optString("term", "");
+ destinationSite.remark = siteJson.optString("remark", "");
+ destinationSite.longitude = siteJson.optString("longitude", "");
+ destinationSite.latitude = siteJson.optString("latitude", "");
+ destinationSite.distance = siteJson.optString("distance", "");
+ truckRouteData.destinationSite = destinationSite;
+ }
+
+ // 解析pathDto
+ if (data.has("pathDto") && !data.isNull("pathDto")) {
+ JSONObject pathJson = data.getJSONObject("pathDto");
+ PathDto pathDto = new PathDto();
+ pathDto.distance = pathJson.optInt("distance", 0);
+ pathDto.duration = pathJson.optInt("duration", 0);
+ pathDto.strategy = pathJson.optString("strategy", "");
+ pathDto.tolls = pathJson.optString("tolls", "");
+ pathDto.toll_distance = pathJson.optInt("toll_distance", 0);
+ pathDto.restriction = pathJson.optInt("restriction", -1);
+ pathDto.traffic_lights = pathJson.optInt("traffic_lights", 0);
+
+ // 解析naviList
+ if (pathJson.has("naviList") && !pathJson.isNull("naviList")) {
+ JSONArray naviArray = pathJson.getJSONArray("naviList");
+ List naviList = new ArrayList<>();
+ for (int i = 0; i < naviArray.length(); i++) {
+ JSONObject naviJson = naviArray.getJSONObject(i);
+ NaviPoint naviPoint = new NaviPoint();
+ naviPoint.name = naviJson.optString("name", "");
+ naviPoint.poiId = naviJson.optString("poiId", "");
+
+ if (naviJson.has("coordinate") && !naviJson.isNull("coordinate")) {
+ JSONObject coordJson = naviJson.getJSONObject("coordinate");
+ Coordinate coordinate = new Coordinate();
+ coordinate.longitude = coordJson.optString("longitude", "");
+ coordinate.latitude = coordJson.optString("latitude", "");
+ naviPoint.coordinate = coordinate;
+ }
+
+ naviList.add(naviPoint);
+ }
+ pathDto.naviList = naviList;
+ }
+
+ truckRouteData.pathDto = pathDto;
+ }
+
+ // 解析algorithmPath
+ if (data.has("algorithmPath") && !data.isNull("algorithmPath")) {
+ JSONObject algorithmJson = data.getJSONObject("algorithmPath");
+ AlgorithmPath algorithmPath = new AlgorithmPath();
+ algorithmPath.tripRecommendationId = algorithmJson.optString("tripRecommendationId", "");
+ algorithmPath.staId = algorithmJson.optString("staId", "");
+ algorithmPath.tripOneWayPathId = algorithmJson.optString("tripOneWayPathId", "");
+ algorithmPath.tripReturnPathId = algorithmJson.optString("tripReturnPathId", "");
+ algorithmPath.oneWayDis = algorithmJson.optString("oneWayDis", "");
+ algorithmPath.returnDis = algorithmJson.optString("returnDis", "");
+ algorithmPath.roundTripDis = algorithmJson.optString("roundTripDis", "");
+ algorithmPath.oneWayTime = algorithmJson.optString("oneWayTime", "");
+ algorithmPath.returnTime = algorithmJson.optString("returnTime", "");
+ algorithmPath.roundTripTime = algorithmJson.optString("roundTripTime", "");
+ algorithmPath.oneWayCost = algorithmJson.optString("oneWayCost", "");
+ algorithmPath.returnCost = algorithmJson.optString("returnCost", "");
+ algorithmPath.roundTripCost = algorithmJson.optString("roundTripCost", "");
+ algorithmPath.oneWayLaborCost = algorithmJson.optString("oneWayLaborCost", "");
+ algorithmPath.returnLaborCost = algorithmJson.optString("returnLaborCost", "");
+ algorithmPath.roundTripLaborCost = algorithmJson.optString("roundTripLaborCost", "");
+ algorithmPath.oneWayChargerouteCost = algorithmJson.optString("oneWayChargerouteCost", "");
+ algorithmPath.returnChargerouteCost = algorithmJson.optString("returnChargerouteCost", "");
+ algorithmPath.roundTripChargerouteCost = algorithmJson.optString("roundTripChargerouteCost", "");
+ algorithmPath.oneWayHydrogenConsumption = algorithmJson.optString("oneWayHydrogenConsumption", "");
+ algorithmPath.returnLaborHydrogenConsumption = algorithmJson.optString("returnLaborHydrogenConsumption", "");
+ algorithmPath.roundTripHydrogenConsumption = algorithmJson.optString("roundTripHydrogenConsumption", "");
+ algorithmPath.oneWayHydrogenCost = algorithmJson.optString("oneWayHydrogenCost", "");
+ algorithmPath.returnLaborHydrogenCost = algorithmJson.optString("returnLaborHydrogenCost", "");
+ algorithmPath.roundTripHydrogenCost = algorithmJson.optString("roundTripHydrogenCost", "");
+ algorithmPath.hydrogenCost = algorithmJson.optString("hydrogenCost", "");
+ algorithmPath.hydrogenStaServiceTime = algorithmJson.optString("hydrogenStaServiceTime", "");
+ algorithmPath.hydrogenStaRefuelingTime = algorithmJson.optString("hydrogenStaRefuelingTime", "");
+ algorithmPath.hydrogenStaQueueTime = algorithmJson.optString("hydrogenStaQueueTime", "");
+ algorithmPath.hydrogenStaServiceTimeCost = algorithmJson.optString("hydrogenStaServiceTimeCost", "");
+ algorithmPath.hydrogenStaRefuelingTimeCost = algorithmJson.optString("hydrogenStaRefuelingTimeCost", "");
+ algorithmPath.hydrogenStaQueueTimeCost = algorithmJson.optString("hydrogenStaQueueTimeCost", "");
+ truckRouteData.algorithmPath = algorithmPath;
+ }
+
+ // 其他字段
+ truckRouteData.truckDtoStr = data.optString("truckDtoStr", null);
+ truckRouteData.isInvokeAlgorithm = data.optBoolean("isInvokeAlgorithm", false);
+
+ return truckRouteData;
+ }
+
+ // 货车路线算法数据Bean
+ public static class TruckRouteData {
+ public TruckDto truckDto;
+ public String truckDtoStr;
+ public DestinationSite destinationSite;
+ public PathDto pathDto;
+ public AlgorithmPath algorithmPath;
+ public boolean isInvokeAlgorithm;
+ }
+
+ public static class TruckDto {
+ public boolean isRestriction;
+ public String mvehicleSizeName;
+ public String mvehicleAxisUnit;
+ public String mcarNumber;
+ public int mcarType;
+ public String mvehicleHeight;
+ public String mvehicleHeightUnit;
+ public String mvehicleWeight;
+ public String mvehicleWeightUnit;
+ public String mvehicleLoad;
+ public String mvehicleLoadUnit;
+ public boolean mvehicleLoadSwitch;
+ public String mvehicleWidth;
+ public String mvehicleWidthUnit;
+ public String mvehicleLength;
+ public String mvehicleLengthUnit;
+ public int mvehicleSize;
+ public int mvehicleAxis;
+ }
+
+ public static class PathDto {
+ public int distance;
+ public int duration;
+ public String strategy;
+ public String tolls;
+ public int toll_distance;
+ public int restriction;
+ public int traffic_lights;
+ public List naviList;
+ }
+
+ public static class NaviPoint {
+ public String name;
+ public String poiId;
+ public Coordinate coordinate;
+ }
+
+ public static class Coordinate {
+ public String longitude;
+ public String latitude;
+ }
+
+ public static class AlgorithmPath {
+ public String tripRecommendationId;
+ public String staId;
+ public String tripOneWayPathId;
+ public String tripReturnPathId;
+ public String oneWayDis;
+ public String returnDis;
+ public String roundTripDis;
+ public String oneWayTime;
+ public String returnTime;
+ public String roundTripTime;
+ public String oneWayCost;
+ public String returnCost;
+ public String roundTripCost;
+ public String oneWayLaborCost;
+ public String returnLaborCost;
+ public String roundTripLaborCost;
+ public String oneWayChargerouteCost;
+ public String returnChargerouteCost;
+ public String roundTripChargerouteCost;
+ public String oneWayHydrogenConsumption;
+ public String returnLaborHydrogenConsumption;
+ public String roundTripHydrogenConsumption;
+ public String oneWayHydrogenCost;
+ public String returnLaborHydrogenCost;
+ public String roundTripHydrogenCost;
+ public String hydrogenCost;
+ public String hydrogenStaServiceTime;
+ public String hydrogenStaRefuelingTime;
+ public String hydrogenStaQueueTime;
+ public String hydrogenStaServiceTimeCost;
+ public String hydrogenStaRefuelingTimeCost;
+ public String hydrogenStaQueueTimeCost;
+ }
+
+ public static class DestinationSite {
+ public String innerSiteId;
+ public String name;
+ public String shortName;
+ public String siteNo;
+ public String city;
+ public String address;
+ public String contact;
+ public String phone;
+ public String type;
+ public String coOpMode;
+ public String booking;
+ public String siteStatus;
+ public String siteStatusName;
+ public String startBusiness;
+ public String endBusiness;
+ public String billingMethod;
+ public String term;
+ public String remark;
+ public String longitude;
+ public String latitude;
+ public String distance;
+ }
+}
diff --git a/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NavigationActivity.java b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NavigationActivity.java
new file mode 100644
index 0000000..65db261
--- /dev/null
+++ b/ln_jq_app/android/app/src/main/java/com/lnkj/ln_jq_app/NavigationActivity.java
@@ -0,0 +1,407 @@
+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 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.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) {
+ }
+}
diff --git a/ln_jq_app/android/app/src/main/res/drawable/car.png b/ln_jq_app/android/app/src/main/res/drawable/car.png
new file mode 100644
index 0000000..5f5ba0a
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/car.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_close.png b/ln_jq_app/android/app/src/main/res/drawable/ic_close.png
new file mode 100644
index 0000000..b934db9
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_close.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_fuel.png b/ln_jq_app/android/app/src/main/res/drawable/ic_fuel.png
new file mode 100644
index 0000000..ec69d9c
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_fuel.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_location.png b/ln_jq_app/android/app/src/main/res/drawable/ic_location.png
new file mode 100644
index 0000000..fa96dc0
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_location.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_marker.png b/ln_jq_app/android/app/src/main/res/drawable/ic_marker.png
new file mode 100644
index 0000000..abafc0a
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_marker.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_mileage.png b/ln_jq_app/android/app/src/main/res/drawable/ic_mileage.png
new file mode 100644
index 0000000..2b1d25d
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_mileage.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_tag.png b/ln_jq_app/android/app/src/main/res/drawable/ic_tag.png
new file mode 100644
index 0000000..dcae650
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_tag.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_time.png b/ln_jq_app/android/app/src/main/res/drawable/ic_time.png
new file mode 100644
index 0000000..38bc14b
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_time.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_toll.png b/ln_jq_app/android/app/src/main/res/drawable/ic_toll.png
new file mode 100644
index 0000000..4ae32e3
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_toll.png differ
diff --git a/ln_jq_app/android/app/src/main/res/drawable/ic_un_marker.png b/ln_jq_app/android/app/src/main/res/drawable/ic_un_marker.png
new file mode 100644
index 0000000..38dc60d
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/drawable/ic_un_marker.png differ
diff --git a/ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.jpg b/ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.jpg
deleted file mode 100644
index 0e5324b..0000000
Binary files a/ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.jpg and /dev/null differ
diff --git a/ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.png b/ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.png
new file mode 100644
index 0000000..1231afa
Binary files /dev/null and b/ln_jq_app/android/app/src/main/res/mipmap-xhdpi/logo.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK.podspec b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK.podspec
new file mode 100644
index 0000000..b5adbfa
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK.podspec
@@ -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/'
+
+ 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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/cal_ruoute_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/cal_ruoute_icon@3x.png
new file mode 100644
index 0000000..65a78f1
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/cal_ruoute_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/car@2x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/car@2x.png
new file mode 100644
index 0000000..04019bc
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/car@2x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/ic_fuel@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/ic_fuel@3x.png
new file mode 100644
index 0000000..a928c03
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/ic_fuel@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/ic_tag@2x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/ic_tag@2x.png
new file mode 100644
index 0000000..dcae650
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/ic_tag@2x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_close@2x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_close@2x.png
new file mode 100644
index 0000000..4bc8e7b
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_close@2x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_dingwei@2x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_dingwei@2x.png
new file mode 100644
index 0000000..ef99e51
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_dingwei@2x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_fanhui@2x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_fanhui@2x.png
new file mode 100644
index 0000000..6117414
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_fanhui@2x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_local@2x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_local@2x.png
new file mode 100644
index 0000000..185c78a
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/icon_local@2x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/locationing_arrow_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/locationing_arrow_icon@3x.png
new file mode 100644
index 0000000..7aff0d6
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/locationing_arrow_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/my_location_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/my_location_icon@3x.png
new file mode 100644
index 0000000..3488026
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/my_location_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_cost_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_cost_icon@3x.png
new file mode 100644
index 0000000..5b4ca78
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_cost_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_distance_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_distance_icon@3x.png
new file mode 100644
index 0000000..aeb9896
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_distance_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_time_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_time_icon@3x.png
new file mode 100644
index 0000000..1803f2c
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/pre_time_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/search_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/search_icon@3x.png
new file mode 100644
index 0000000..47cb9b9
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/search_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/station_normal_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/station_normal_icon@3x.png
new file mode 100644
index 0000000..720eec2
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/station_normal_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/station_select_icon@3x.png b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/station_select_icon@3x.png
new file mode 100644
index 0000000..9654ffa
Binary files /dev/null and b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/AMapNavIOSSDK.bundle/station_select_icon@3x.png differ
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/PrivacyInfo.xcprivacy b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/PrivacyInfo.xcprivacy
new file mode 100644
index 0000000..755391a
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Assets/PrivacyInfo.xcprivacy
@@ -0,0 +1,31 @@
+
+
+
+
+ NSPrivacyTracking
+
+ NSPrivacyTrackingDomains
+
+ NSPrivacyAccessedAPITypes
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryFileTimestamp
+ NSPrivacyAccessedAPITypeReasons
+
+ 0A2A.1
+
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryDiskSpace
+ NSPrivacyAccessedAPITypeReasons
+
+ 85F4.1
+
+
+
+ NSPrivacyCollectedDataTypes
+
+
+
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ABaseViewController.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ABaseViewController.h
new file mode 100644
index 0000000..062a4fe
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ABaseViewController.h
@@ -0,0 +1,24 @@
+//
+// ABaseViewController.h
+// ANavDemo
+//
+// Created by admin on 2026/2/5.
+//
+
+#import
+#import
+#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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ABaseViewController.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ABaseViewController.m
new file mode 100644
index 0000000..8aabf2b
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ABaseViewController.m
@@ -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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ACustomAnnotationView.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ACustomAnnotationView.h
new file mode 100644
index 0000000..4d6453a
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ACustomAnnotationView.h
@@ -0,0 +1,17 @@
+//
+// ACustomAnnotationView.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/17.
+//
+
+#import
+#import "AMapNavCommonUtil.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ACustomAnnotationView : MAAnnotationView
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ACustomAnnotationView.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ACustomAnnotationView.m
new file mode 100644
index 0000000..5f81c4b
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ACustomAnnotationView.m
@@ -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)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)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 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 *)touches withEvent:(UIEvent *)event {
+ [super touchesBegan:touches withEvent:event];
+
+ id 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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKHeader.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKHeader.h
new file mode 100644
index 0000000..eea9824
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKHeader.h
@@ -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 */
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKManager.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKManager.h
new file mode 100644
index 0000000..e2e227b
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKManager.h
@@ -0,0 +1,29 @@
+//
+// AMapNavSDKManager.h
+// Pods
+//
+// Created by admin on 2026/2/10.
+//
+
+#import
+
+#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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKManager.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKManager.m
new file mode 100644
index 0000000..9f3378b
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AMapNavSDKManager.m
@@ -0,0 +1,72 @@
+//
+// AMapNavSDKManager.m
+// Pods
+//
+// Created by admin on 2026/2/10.
+//
+
+#import "AMapNavSDKManager.h"
+#import "AMapPrivacyUtility.h"
+
+#import
+
+@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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ARoutePlaneController.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ARoutePlaneController.h
new file mode 100644
index 0000000..3a34308
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ARoutePlaneController.h
@@ -0,0 +1,28 @@
+//
+// ARoutePlaneController.h
+// ANavDemo
+//
+// Created by admin on 2026/2/5.
+//
+
+#import
+#import "ABaseViewController.h"
+
+#import "SelectableOverlay.h"
+#import "NaviPointAnnotation.h"
+
+#import
+
+#import "ACustomAnnotationView.h"
+#import "AMapNavSDKHeader.h"
+#import "ACustomPointAnnotation.h"
+#import "ATripCalcResponse.h"
+#import "AMapNavCommonUtil.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ARoutePlaneController : ABaseViewController
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ARoutePlaneController.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ARoutePlaneController.m
new file mode 100644
index 0000000..6c7dc49
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ARoutePlaneController.m
@@ -0,0 +1,1186 @@
+//
+// ARoutePlaneController.m
+// ANavDemo
+//
+// Created by admin on 2026/2/5.
+//
+
+#import "ARoutePlaneController.h"
+
+#import
+#import
+#import
+#import
+
+#import "ASearchAddressController.h"
+
+#import "AMapNavSDKManager.h"
+
+#import "AMapPrivacyUtility.h"
+
+#import "AStationDetailPopupController.h"
+
+#define kRouteIndicatorViewHeight 64.f
+
+#import "AMapHyStationModel.h"
+#import "AMapNavHttpUtil.h"
+#import "ACustomStepView.h"
+#import "ABottomBarView.h"
+
+@interface ARoutePlaneController ()
+@property (nonatomic, strong) UITextField *textField;
+
+/// 底部搜索+规划路线栏
+@property (nonatomic, strong) ABottomBarView *bottomBarView;
+
+@property (nonatomic, strong) MAMapView *mapView;
+@property (nonatomic,strong) AMapLocationManager *locationService; //定位服务
+
+/**
+ * 经纬度
+ */
+@property (nonatomic, assign) double latitude;
+@property (nonatomic, assign) double longitude;
+
+
+@property (nonatomic, strong) UITextField *startTf;
+@property (nonatomic, strong) UITextField *dstTf;
+@property (nonatomic, strong) UIButton *navBtn;
+
+@property (nonatomic, strong) AMapPOI *startPoi;
+@property (nonatomic, strong) AMapPOI *dstPoi;
+@property (nonatomic, strong) AMapPOI *defaultDstPoi; //默认的附近点
+
+@property (nonatomic, strong) AMapNaviCompositeManager *compositeManager;//nav
+@property (nonatomic, assign) BOOL calRouteSuccess;
+
+@property (nonatomic, strong) NSDictionary * currentCalRoutePaths;//当前规划的路线
+@property (nonatomic , assign)BOOL isStartNav;//开始导航
+@property (nonatomic, strong) NSArray * lastOverLays;
+
+
+@property (nonatomic , strong)NSArray * hyStationArr;//站点数据
+@property (nonatomic , assign)BOOL startQueryCurrnetNodeFlag;//开始查询当前节点;
+
+@property (nonatomic , strong)ACustomStepView * stepView;
+
+/// 当前弹出的站点详情弹框
+@property (nonatomic , strong)AStationDetailPopupController * stationDetailPopup;
+@property (nonatomic, strong) ANavPointModel *pointModel; //当前选的目的点
+@property (nonatomic, strong) ATripCalcDataModel * tjdPathInfoModel;//途经点信息
+
+@property (nonatomic, strong) CLLocationManager * locationManager;
+@end
+
+@implementation ARoutePlaneController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ _startQueryCurrnetNodeFlag = NO;
+
+ [self observePrivacyStatus];
+ [self checkPrivacyStatus];
+
+ ////
+// [self.naviManager independentCalculateDriveRouteWithStartPOIInfo:startPOIInfo
+// endPOIInfo:endPOIInfo
+// wayPOIInfos:wayPOIInfos
+// strategy:AMapNaviDrivingStrategyMultipleDefault
+// callback:^(AMapNaviRouteGroup *routeGroup, NSError *error) {
+// if (error == nil) {
+// // 算路成功,routeGroup 包含路线数据
+// [self startNaviWithRoute:routeGroup];
+// }
+// }];
+
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+
+
+ [AMapPrivacyUtility handlePrivacyAgreeStatusIn:self];
+
+}
+
+#pragma mark - request
+-(void)requestHyListWithParms:(NSDictionary*)dic {
+ NSString * url = kGetStationListUrl;
+
+ /**
+ //汽车公园有数据
+ "longitude": "121.30461400",
+ "latitude": "31.17321100"
+ */
+ NSDictionary * dic2 = @{@"longitude":@"121.16661700" , @"latitude":@"31.27981600"};
+
+
+ [AMapNavHttpUtil postRequestWithURL:url parameters:dic requestHeader:@{@"Content-Type":@"application/json; charset=UTF-8"} successHandler:^(NSDictionary * _Nonnull data, NSURLResponse * _Nonnull response) {
+ AMapHyResponse * resp = [AMapHyResponse mj_objectWithKeyValues:data];
+ if (resp.code == 0 && resp.data) {
+ NSArray * allData = resp.data;
+ NSArray * dst = allData;
+ NSInteger len = allData.count;
+ if (allData.count > len) {
+ dst = [resp.data subarrayWithRange:NSMakeRange(0, len)];
+ }
+
+ [self updateMapAnnotationWithData:dst];
+ }else {
+ NSLog(@">>>>>>>请求站点:%@" ,resp.message);
+ }
+
+ } failureHandler:^(NSError * _Nonnull error) {
+ NSLog(@">>>>>>>请求站点err:%@" ,error.debugDescription);
+ }];
+}
+
+-(void)requestHyDetailWithParms:(NSDictionary*)dic {
+ NSString * url = kGetStationDetailtUrl;
+
+ [AMapNavHttpUtil postRequestWithURL:url parameters:dic requestHeader:@{@"Content-Type":@"application/json; charset=UTF-8"} successHandler:^(NSDictionary * _Nonnull data, NSURLResponse * _Nonnull response) {
+ AMapHyResponse * resp = [AMapHyResponse mj_objectWithKeyValues:data];
+ if (resp.code == 0) {
+ NSDictionary * resData = data[@"data"];
+ AMapHyStationModel * station = [AMapHyStationModel mj_objectWithKeyValues:resData];
+ [self updateHeadAddressWithStation:station];
+ }else {
+ NSLog(@">>>>>>>请求站点detail:%@" ,resp.message);
+ }
+
+ } failureHandler:^(NSError * _Nonnull error) {
+ NSLog(@">>>>>>>请求站点err:%@" ,error.debugDescription);
+ }];
+
+}
+
+
+-(void)requestRoutePathWithParms:(NSDictionary*)dic completeHandle:(void(^)(ATripCalcDataModel * tjd))blk {
+
+ NSString * token = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.token"];
+ NSString * carNo = [[NSUserDefaults standardUserDefaults]valueForKey:@"flutter.plateNumber"];
+ if (stringIsEmpty(token) || stringIsEmpty(carNo)) {
+ [AMapNavCommonUtil showMsg:@"车牌或token为空,请重新登录"]; return;
+ }
+
+ if (stringIsEmpty(self.pointModel.stationID)) {
+ [AMapNavCommonUtil showMsg:@"站点ID不能为空"]; return;
+ }
+
+ NSMutableDictionary * dic2 = [NSMutableDictionary dictionary];
+ dic2[@"longitude"] = [NSString stringWithFormat:@"%f" , self.pointModel.coordinate.longitude];
+ dic2[@"latitude"] = [NSString stringWithFormat:@"%f" , self.pointModel.coordinate.latitude];
+ dic2[@"plateNumber"] = carNo;
+ dic2[@"hydrogenSiteId"] = [NSString stringWithFormat:@"%@" , self.pointModel.stationID];
+
+ NSDictionary * headDic = @{
+ @"Content-Type":@"application/json; charset=UTF-8",
+ @"asoco-token" : token
+ };
+
+
+
+ [AMapNavCommonUtil showLoadingWithMsg:@""];
+
+ NSString * url = kGetRoutePointtUrl;
+ [AMapNavHttpUtil postRequestWithURL:url parameters:dic2 requestHeader:headDic successHandler:^(NSDictionary * _Nonnull data, NSURLResponse * _Nonnull response) {
+
+ ATripCalcResponse * resp = [ATripCalcResponse mj_objectWithKeyValues:data];
+ if (resp.code == 200 && resp.data) {
+ self.tjdPathInfoModel = resp.data;
+
+ if (blk) {
+ blk(resp.data);
+ }
+ }else {
+ [AMapNavCommonUtil showMsg:data[@"message"]];
+ }
+
+ [AMapNavCommonUtil dismiss];
+
+ } failureHandler:^(NSError * _Nonnull error) {
+ NSLog(@">>>>>>>请求途经点:%@" ,error.debugDescription);
+ [AMapNavCommonUtil dismiss];
+ [AMapNavCommonUtil showMsg:error.debugDescription];
+ }];
+}
+
+
+-(void)updateHeadAddressWithStation:(AMapHyStationModel*)model {
+
+ AMapPOI * aoi = [[AMapPOI alloc] init];
+
+ aoi.location = [AMapGeoPoint locationWithLatitude:[model.latitude doubleValue] longitude:[model.longitude doubleValue]];
+
+ aoi.name = model.name;
+ aoi.address = model.address;
+ aoi.uid = model.ID;
+
+ self.dstPoi = aoi;
+ self.defaultDstPoi = [aoi copy];
+
+ ///地址栏
+ [self updateUIWithData:aoi textField:self.dstTf];
+
+}
+
+-(void)updateMapAnnotationWithData:(NSArray *)dataArr {
+ self.hyStationArr = dataArr;
+ if (!(dataArr && dataArr.count > 0)) {
+ return;
+ }
+
+ ///添加标注
+ NSMutableArray * points = [NSMutableArray arrayWithCapacity:dataArr.count];
+ for (AMapHyStationModel * model in dataArr) {
+ ACustomPointAnnotation *pointAnnotation = [[ACustomPointAnnotation alloc] init];
+ if (!(model.latitude && model.longitude)) {
+ continue;
+ }
+
+ pointAnnotation.coordinate = CLLocationCoordinate2DMake([model.latitude doubleValue], [model.longitude doubleValue]);
+ pointAnnotation.title = model.name;
+ pointAnnotation.subtitle = model.address;
+ pointAnnotation.stationID = model.ID;
+
+ [points addObject:pointAnnotation];
+ }
+
+ // 1. 先获取当前地图区域
+// MACoordinateRegion currentRegion = self.mapView.region;
+
+ // 2. 添加标注但不改变地图显示
+ [self.mapView addAnnotations:points];
+
+ // 保持地图中心点为当前位置
+ if (self.latitude && self.longitude) {
+ [self.mapView setCenterCoordinate: CLLocationCoordinate2DMake(self.latitude, self.longitude) animated:YES];
+ }
+
+ // 3. 保持当前区域不变
+// [self.mapView setRegion:currentRegion animated:NO];
+
+
+}
+
+#pragma mark - init
+-(void)initSubview {
+
+ // ── 地图全屏 ─────────────────────────────────────────────
+ [self.mapView mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.edges.equalTo(self.view);
+ }];
+
+ // ── 底部栏(搜索框 + 规划路线按钮) ──────────────────────
+ ABottomBarView *bottomBar = [[ABottomBarView alloc] init];
+ bottomBar.delegate = self;
+ [self.view addSubview:bottomBar];
+ self.bottomBarView = bottomBar;
+
+ [bottomBar mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.left.right.equalTo(self.view);
+ make.bottom.equalTo(self.view);
+ }];
+
+ // ── 兼容旧逻辑:保留 startTf / dstTf 属性(隐藏,不再显示) ──
+ // startTf/dstTf 只用作数据载体,不加入视图层级
+ UITextField *startTf = [[UITextField alloc] init];
+ startTf.tag = 100;
+ self.startTf = startTf;
+
+ UITextField *dstTf = [[UITextField alloc] init];
+ dstTf.tag = 200;
+ self.dstTf = dstTf;
+
+ // ── 旧版导航按钮(保留,hidden 状态,供兼容逻辑使用) ─────
+ UIButton *navBtn = [UIButton buttonWithType:UIButtonTypeCustom];
+ navBtn.hidden = YES;
+ [navBtn addTarget:self action:@selector(navAction) forControlEvents:UIControlEventTouchUpInside];
+ [self.view addSubview:navBtn];
+ [navBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.right.equalTo(self.view).offset(-15);
+ make.bottom.equalTo(self.view).offset(-118);
+ make.height.mas_equalTo(@30);
+ make.width.mas_equalTo(@60);
+ }];
+
+ [self.view bringSubviewToFront:bottomBar];
+
+ // ── 放大缩小控件 ──────────────────────────────────────────
+ ACustomStepView *stepView = [[ACustomStepView new] initWithValue:self.mapView.zoomLevel maxValue:18.5 min:3.5];
+ [self.view addSubview:stepView];
+ [stepView mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.right.equalTo(self.view).offset(-15);
+ make.width.equalTo(@40);
+ make.height.equalTo(@81);
+ make.top.equalTo(self.view).offset(100);
+ }];
+
+ [stepView addObserver:self forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:nil];
+ self.stepView = stepView;
+
+
+ ///当前位置按钮
+ UIButton * btn = [UIButton buttonWithType:UIButtonTypeCustom];
+ [btn setImage:[AMapNavCommonUtil imageWithName3x:@"my_location_icon"] forState:UIControlStateNormal];
+ btn.backgroundColor = [UIColor lightGrayColor];
+ btn.titleLabel.font = [UIFont systemFontOfSize:14];
+ btn.layer.cornerRadius = 20;
+
+ [btn addTarget:self action:@selector(updateUserLocalAction) forControlEvents:UIControlEventTouchUpInside];
+
+ [self.view addSubview:btn];
+ [btn mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.right.equalTo(self.view).offset(-10);
+ make.width.height.equalTo(@40);
+ make.bottom.equalTo(bottomBar.mas_top).offset(-85);
+ }];
+
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+ if ([keyPath isEqualToString:@"value"]) {
+ self.mapView.zoomLevel = [change[NSKeyValueChangeNewKey] doubleValue];
+ }
+
+}
+
+
+- (void)initDriveManager
+{
+ //请在 dealloc 函数中执行 [AMapNaviDriveManager destroyInstance] 来销毁单例
+ [[AMapNaviDriveManager sharedInstance] setDelegate:self];
+}
+
+
+- (void)initMapView
+{
+ if (self.mapView == nil)
+ {
+ self.mapView = [[MAMapView alloc] initWithFrame:CGRectZero];
+ [self.mapView setDelegate:self];
+ self.mapView.showsUserLocation = YES;
+ self.mapView.userTrackingMode = MAUserTrackingModeFollowWithHeading;
+ self.mapView.desiredAccuracy = kCLLocationAccuracyNearestTenMeters; // 定位精度
+ _mapView.showsScale= YES;
+
+ _mapView.logoCenter = CGPointMake(CGRectGetWidth(self.view.bounds)-55, 450);
+ self.mapView.zoomLevel = 11;
+
+ // 4. 禁用自动调整
+// [self.mapView setAutoCheckMapBoundary:NO];
+
+ [self.view addSubview:self.mapView];
+
+ if (@available(iOS 14.0, *)) {
+ // iOS14+ 需要额外处理
+ CLAuthorizationStatus status = [[[CLLocationManager alloc] init] authorizationStatus];
+ if (status == kCLAuthorizationStatusNotDetermined) {
+ [[[CLLocationManager alloc] init] requestWhenInUseAuthorization];
+ }
+ }
+
+ }
+}
+
+-(void)updateUserLocalAction {
+ // 如果已经有位置,直接移动视角
+ if (_mapView.userLocation.location) {
+ CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(self.latitude, self.longitude);
+
+ [_mapView setCenterCoordinate:coord animated:YES];
+// [_mapView setZoomLevel:10 animated:YES];
+ } else {
+ // 如果尚未获取到位置,进入跟踪模式等待回调
+ [_mapView setUserTrackingMode:MAUserTrackingModeFollow animated:YES];
+ }
+
+}
+
+- (void)initAnnotations
+{
+ NaviPointAnnotation *beginAnnotation = [[NaviPointAnnotation alloc] init];
+ [beginAnnotation setCoordinate:CLLocationCoordinate2DMake(self.startPoi.location.latitude, self.startPoi.location.longitude)];
+ beginAnnotation.title = @"起始点";
+ beginAnnotation.navPointType = NaviPointAnnotationStart;
+
+ [self.mapView addAnnotation:beginAnnotation];
+
+// NaviPointAnnotation *endAnnotation = [[NaviPointAnnotation alloc] init];
+// [endAnnotation setCoordinate:CLLocationCoordinate2DMake(self.dstPoi.location.latitude, self.dstPoi.location.longitude)];
+// endAnnotation.title = @"终点";
+// endAnnotation.navPointType = NaviPointAnnotationEnd;
+//
+// [self.mapView addAnnotation:endAnnotation];
+}
+
+
+
+
+- (AMapLocationManager *)locationService {
+ if (!_locationService) {
+ _locationService = [[AMapLocationManager alloc] init];
+ _locationService.delegate = self;
+ _locationService.desiredAccuracy = kCLLocationAccuracyBestForNavigation; // 最高精度模式
+ _locationService.distanceFilter = 5;
+ _locationService.locatingWithReGeocode = YES;
+
+// CLLocationManager * mgr;
+// // 2. 检查是否为模糊定位(iOS 14+)
+// if (@available(iOS 14.0, *)) {
+// if (mgr.accuracyAuthorization == CLAccuracyAuthorizationReducedAccuracy) {
+// // 弹窗申请临时精确权限(需配置 purposeKey)
+// [mgr requestTemporaryFullAccuracyAuthorizationWithPurposeKey:@"YourPurposeKey" completion:nil];
+//
+// }
+// }
+
+ }
+ return _locationService;
+}
+
+
+
+- (void)dealloc {
+ [self.locationService stopUpdatingLocation];
+ [self.stepView removeObserver:self forKeyPath:@"value"];
+
+}
+
+- (AMapNaviCompositeManager *)compositeManager {
+
+ if (!_compositeManager) {
+ _compositeManager = [[AMapNaviCompositeManager alloc] init]; // 初始化
+ _compositeManager.delegate = self; // 如果需要使用AMapNaviCompositeManagerDelegate的相关回调(如自定义语音、获取实时位置等),需要设置delegate
+ }
+ return _compositeManager;
+}
+
+// 监听隐私状态变化
+- (void)observePrivacyStatus {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(handlePrivacyUpdate)
+ name:@"ksAMapPrivacyDidUpdateNotification" // 自定义通知
+ object:nil];
+}
+
+-(void)handlePrivacyUpdate {
+ [self checkPrivacyStatus];
+
+}
+
+// 检查当前隐私状态
+- (void)checkPrivacyStatus {
+ BOOL hasAgreed = [[NSUserDefaults standardUserDefaults] boolForKey:@"usragreeStatus"];
+
+ if (hasAgreed) {
+
+ /// 开启定位
+ [self.locationService startUpdatingLocation];
+// [self.mapView reloadMap];
+
+ [self initMapView];
+ [self initSubview];
+
+ [self initDriveManager];
+
+ } else {
+
+ }
+}
+
+#pragma mark - 计算线路
+///计算线路
+-(void)calRoutePathWithWayPonts:(NSArray *) naviList {
+ AMapNaviPoint * startPoint = [AMapNaviPoint locationWithLatitude:self.startPoi.location.latitude longitude:self.startPoi.location.longitude];
+
+
+ AMapPOI *_dstPoi = self.dstPoi ? self.dstPoi : self.defaultDstPoi;
+ AMapNaviPoint * endPoint = [AMapNaviPoint locationWithLatitude:_dstPoi.location.latitude longitude:_dstPoi.location.longitude];
+
+ if (!(self.startPoi && _dstPoi)) {
+ [AMapNavCommonUtil showMsg:@"计算线路,起始点缺失"];
+ return;
+ }
+
+ AMapNaviDrivingStrategy strategy = ConvertDrivingPreferenceToDrivingStrategy(0,
+ 0,
+ 0,
+ 0,
+ 0);
+ strategy = AMapNaviDrivingStrategyMultipleDefault;//使用默认策略
+
+ id delegate = [AMapNaviDriveManager sharedInstance].delegate;
+ if (!delegate) {
+ [AMapNaviDriveManager sharedInstance].delegate = self;
+ }
+
+ NSArray *wayPoints = [self getWayPointWithPoint:naviList];
+
+ [[AMapNaviDriveManager sharedInstance] setVehicleInfo:[self getCarInfo]];
+ [[AMapNaviDriveManager sharedInstance] calculateDriveRouteWithStartPoints:@[startPoint]
+ endPoints:@[endPoint]
+ wayPoints:wayPoints
+ drivingStrategy:strategy];
+
+}
+
+-(NSArray *)getWayPointWithPoint:(NSArray *) naviList {
+ NSMutableArray *wayPoints = [NSMutableArray array];
+
+ for (int i = 0; i < naviList.count; i++) {
+ ANaviPathInfoModel * item = naviList[i];
+
+ AMapNaviPoint * res = [AMapNaviPoint locationWithLatitude:[item.latitude doubleValue] longitude:[item.longitude doubleValue]];
+ [wayPoints addObject:res];
+ }
+
+ return wayPoints;
+}
+
+-(AMapNaviVehicleInfo*)getCarInfo {
+ AMapNaviVehicleInfo * car = [AMapNaviVehicleInfo new];
+
+ ATruckModel * truckInfo = self.tjdPathInfoModel.truckDto;
+ if (!truckInfo) {
+ return car;
+ }
+
+ // 车牌号
+ if (truckInfo.mcarNumber.length > 0) {
+ car.vehicleId = truckInfo.mcarNumber;
+ }
+
+ // 是否躲避限行
+ car.isETARestriction = truckInfo.isRestriction;
+
+ // 车辆类型(mcarType 与高德 type 定义一致)
+ car.type = truckInfo.mcarType;
+
+ // 货车大小分类(mvehicleSize:4 = 重型货车,对应高德 size:4)
+ car.size = truckInfo.mvehicleSize;
+
+ // 轴数
+ car.axisNums = truckInfo.mvehicleAxis;
+
+ // 车身宽度(单位:米)
+ if (truckInfo.mvehicleWidth.length > 0) {
+ car.width = [[NSDecimalNumber decimalNumberWithString:truckInfo.mvehicleWidth] floatValue];
+ }
+
+ // 车身高度(单位:米)
+ if (truckInfo.mvehicleHeight.length > 0) {
+ car.height = [[NSDecimalNumber decimalNumberWithString:truckInfo.mvehicleHeight] floatValue];
+ }
+
+ // 车身长度(单位:米)
+ if (truckInfo.mvehicleLength.length > 0) {
+ car.length = [[NSDecimalNumber decimalNumberWithString:truckInfo.mvehicleLength] floatValue];
+ }
+
+ //总重
+ if (truckInfo.mvehicleLoad.length > 0) {
+ car.load = [[NSDecimalNumber decimalNumberWithString:truckInfo.mvehicleLoad] floatValue];
+ }
+
+ // 核定载重
+ if (truckInfo.mvehicleWeight.length > 0) {
+ car.weight = [[NSDecimalNumber decimalNumberWithString:truckInfo.mvehicleWeight] floatValue];
+ }
+
+ return car;
+}
+
+///算路成功后回调
+- (void)driveManagerOnCalculateRouteSuccess:(AMapNaviDriveManager *)driveManager
+{
+ NSDictionary * lines = [AMapNaviDriveManager sharedInstance].naviRoutes ;
+ NSLog(@"onCalculateRouteSuccess:%@" , lines);
+ //算路成功后显示路径
+ [self gd_navWithCalulatedPath];
+}
+
+- (void)driveManager:(AMapNaviDriveManager *)driveManager error:(NSError *)error {
+ ///算路失败,直接去高德规划页面
+ [self gd_calPathWithNoStationId:self.pointModel];
+}
+
+#pragma mark 显示线路[废弃]
+- (void)showNaviRoutes
+{
+ if ([[AMapNaviDriveManager sharedInstance].naviRoutes count] <= 0)
+ {
+ return;
+ }
+
+ self.lastOverLays = self.mapView.overlays;
+ [self.mapView removeOverlays:self.mapView.overlays];
+// [self.routeIndicatorInfoArray removeAllObjects];
+
+ self.currentCalRoutePaths = [AMapNaviDriveManager sharedInstance].naviRoutes;
+
+
+ NSInteger routeId = 0;
+
+ //将路径显示到地图上
+ for (NSNumber *aRouteID in [[AMapNaviDriveManager sharedInstance].naviRoutes allKeys])
+ {
+ AMapNaviRoute *aRoute = [[[AMapNaviDriveManager sharedInstance] naviRoutes] objectForKey:aRouteID];
+ int count = (int)[[aRoute routeCoordinates] count];
+
+ //添加路径Polyline
+ CLLocationCoordinate2D *coords = (CLLocationCoordinate2D *)malloc(count * sizeof(CLLocationCoordinate2D));
+ for (int i = 0; i < count; i++)
+ {
+ AMapNaviPoint *coordinate = [[aRoute routeCoordinates] objectAtIndex:i];
+ coords[i].latitude = [coordinate latitude];
+ coords[i].longitude = [coordinate longitude];
+ }
+
+ // SelectableOverlay 继承 MAPolyline,直接用坐标初始化,避免 renderer overlay 不匹配警告
+ SelectableOverlay *selectablePolyline = [SelectableOverlay overlayWithCoordinates:coords count:count];
+ selectablePolyline.routeID = [aRouteID integerValue];
+
+ [self.mapView addOverlay:selectablePolyline];
+ free(coords);
+
+ routeId = [aRouteID integerValue];
+
+ }
+
+ // 1. 先获取当前地图区域
+ MACoordinateRegion currentRegion = self.mapView.region;
+
+ [self.mapView showAnnotations:self.mapView.annotations animated:NO];
+
+ // 3. 保持当前区域不变
+ [self.mapView setRegion:currentRegion animated:NO];
+
+ [self selectNaviRouteWithID:routeId];
+
+ ///如果已开始导航,直接进入导航
+// if (self.isStartNav) {
+// [self gd_navWithCalulatedPath];
+// }
+
+}
+
+- (void)selectNaviRouteWithID:(NSInteger)routeID
+{
+ //在开始导航前进行路径选择
+ if ([[AMapNaviDriveManager sharedInstance] selectNaviRouteWithRouteID:routeID])
+ {
+ [self selecteOverlayWithRouteID:routeID];
+ }
+ else
+ {
+ NSLog(@"路径选择失败!");
+ }
+}
+
+- (void)selecteOverlayWithRouteID:(NSInteger)routeID
+{
+ [self.mapView.overlays enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id overlay, NSUInteger idx, BOOL *stop)
+ {
+ if ([overlay isKindOfClass:[SelectableOverlay class]])
+ {
+ SelectableOverlay *selectableOverlay = overlay;
+
+ /* 获取overlay对应的renderer. */
+ MAPolylineRenderer * overlayRenderer = (MAPolylineRenderer *)[self.mapView rendererForOverlay:selectableOverlay];
+
+ if (selectableOverlay.routeID == routeID)
+ {
+ /* 设置选中状态. */
+ selectableOverlay.selected = YES;
+
+ /* 修改renderer选中颜色. */
+ overlayRenderer.fillColor = selectableOverlay.selectedColor;
+ overlayRenderer.strokeColor = selectableOverlay.selectedColor;
+
+ /* 修改overlay覆盖的顺序. */
+ [self.mapView exchangeOverlayAtIndex:idx withOverlayAtIndex:self.mapView.overlays.count - 1];
+ }
+ else
+ {
+ /* 设置选中状态. */
+ selectableOverlay.selected = NO;
+
+ /* 修改renderer选中颜色. */
+ overlayRenderer.fillColor = selectableOverlay.regularColor;
+ overlayRenderer.strokeColor = selectableOverlay.regularColor;
+ }
+ }
+ }];
+}
+
+#pragma mark - 导航
+///选择方式
+-(void)navAction {
+ [self gd_navWithCalulatedPath]; //直接导航
+}
+
+-(void)showSelectNavType {
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"选择导航类型" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
+ UIAlertAction *sure = [UIAlertAction actionWithTitle:@"SDK导航" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+ [self gd_navWithCalulatedPath];
+ }];
+ UIAlertAction *sure2 = [UIAlertAction actionWithTitle:@"高德地图导航" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+
+ [self navigationType_app];
+ }];
+
+ UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
+ }];
+
+ [alert addAction:sure];
+ [alert addAction:sure2];
+ [alert addAction:cancel];
+
+ [self presentViewController:alert animated:YES completion:nil];
+}
+
+-(void)navigationType_app {
+
+ NSURL* scheme = [NSURL URLWithString:@"iosamap://"];
+ BOOL canOpen = [[UIApplication sharedApplication] canOpenURL:scheme];
+ if (!canOpen) {
+ [self showAlertWithMessage:@"请先安装高德地图客户端"]; return;
+ }
+
+ NSString *myLocationScheme = [NSString stringWithFormat:@"iosamap://navi?sourceApplication=ANavDemo&lat=31.2304&lon=121.4737&t=0&dev=1"];
+
+ NSString *encodedUrlString = [myLocationScheme stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
+
+ NSURL *gaodeUrl = [NSURL URLWithString:encodedUrlString];
+
+
+ [[UIApplication sharedApplication] openURL:gaodeUrl options:@{} completionHandler:^(BOOL res) {
+
+ }];
+
+}
+
+///使用算好的路,直接导航
+-(void)gd_navWithCalulatedPath {
+ id delegate = [AMapNaviDriveManager sharedInstance].delegate;
+ if (!delegate) {
+ [AMapNaviDriveManager sharedInstance].delegate = self;
+ }
+
+ AMapNaviCompositeUserConfig *config = [[AMapNaviCompositeUserConfig alloc] init];
+
+ [config setRoutePlanPOIType:AMapNaviRoutePlanPOITypeStart location:[AMapNaviPoint locationWithLatitude:self.startPoi.location.latitude longitude:self.startPoi.location.longitude] name:self.startPoi.name POIId:nil];
+
+ ///车辆信息
+ [config setVehicleInfo:[self getCarInfo]];
+
+ //直接进入导航,不在算路
+ [config setStartNaviDirectly:YES]; //直接进入导航界面
+ [config setNeedCalculateRouteWhenPresent:NO];//不在算路
+ [config setMultipleRouteNaviMode:NO];//直接单线路径导航
+// [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO];
+
+ [self.compositeManager presentRoutePlanViewControllerWithOptions:config];
+}
+
+///SDK计算线路
+-(void)gd_calPathWithNoStationId: (ANavPointModel *) dst {
+
+ AMapNaviCompositeUserConfig *config = [[AMapNaviCompositeUserConfig alloc] init];
+
+ [config setRoutePlanPOIType:AMapNaviRoutePlanPOITypeEnd location:[AMapNaviPoint locationWithLatitude:dst.coordinate.latitude longitude:dst.coordinate.longitude] name:dst.name POIId:nil];
+ [config setVehicleInfo:[self getCarInfo]];
+
+// [config setStartNaviDirectly:YES]; //直接进入导航界面
+ [config setNeedCalculateRouteWhenPresent:NO];//不在算路
+// [config setMultipleRouteNaviMode:NO];//直接单线路径导航
+// [config setNeedDestoryDriveManagerInstanceWhenDismiss:NO];
+
+ [self.compositeManager presentRoutePlanViewControllerWithOptions:config];
+}
+
+#pragma mark - AMapLocationManagerDelegate
+- (void)amapLocationManager:(AMapLocationManager *)manager didUpdateLocation:(CLLocation *)location reGeocode:(AMapLocationReGeocode *)reGeocode
+{
+ if (!location) {
+ return;
+ }
+ self.latitude = location.coordinate.latitude;
+ self.longitude = location.coordinate.longitude;
+
+ AMapNavSDKManager * sdk = [AMapNavSDKManager sharedManager];
+ sdk.localCity = reGeocode.city;
+ sdk.locationAddressDetail = reGeocode.POIName;
+
+ // 设置地图中心为用户位置
+// MACoordinateRegion region = MACoordinateRegionMake(location.coordinate,
+// MACoordinateSpanMake(0.1, 0.1));
+// [self.mapView setRegion:region animated:YES];
+
+
+ //更新出发点
+ AMapPOI * aoi = [[AMapPOI alloc] init];
+#ifdef kAMapSDKDebugFlag
+ aoi.location = [AMapGeoPoint locationWithLatitude:31.23 longitude:121.48 ];
+ aoi.name =@"人民大道185号";
+ self.latitude = 31.23;
+ self.longitude = 121.48 ;
+#else
+ aoi.location = [AMapGeoPoint locationWithLatitude:self.latitude longitude:self.longitude ];
+ aoi.name = reGeocode.POIName;
+#endif
+
+ self.startPoi = aoi;
+ [self updateUIWithData:aoi textField:self.startTf];
+
+ //获取附近站点
+ if (!self.startQueryCurrnetNodeFlag && reGeocode) {
+ self.startQueryCurrnetNodeFlag = YES;
+ NSString * province = reGeocode.province;
+ NSString * city = reGeocode.city;
+ NSString * district = reGeocode.district;
+ NSString * longitude = [NSString stringWithFormat:@"%f",self.longitude];
+ NSString * latitude = [NSString stringWithFormat:@"%f",self.latitude];
+
+ if (province && city && district) {
+ NSDictionary * dic = @{@"province":province , @"city":city , @"district":district , @"longitude":longitude , @"latitude":latitude};
+
+ [self requestHyDetailWithParms:dic];
+ }
+
+ [self requestHyListWithParms:@{@"longitude":longitude , @"latitude":latitude}];
+ }
+
+
+}
+
+#pragma mark - MAMapView 渲染
+
+- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id)annotation
+{
+ // 当前位置:用 car 图标替换系统默认蓝点
+ if ([annotation isKindOfClass:[MAUserLocation class]])
+ {
+ static NSString *userLocationIdentifier = @"UserLocationCarIdentifier";
+ MAAnnotationView *userView = [mapView dequeueReusableAnnotationViewWithIdentifier:userLocationIdentifier];
+ if (userView == nil)
+ {
+ userView = [[MAAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:userLocationIdentifier];
+ userView.canShowCallout = NO;
+ }
+ else
+ {
+ userView.annotation = annotation;
+ }
+ userView.image = [AMapNavCommonUtil imageWithName:@"car"];
+ return userView;
+ }
+
+ if ([annotation isKindOfClass:[NaviPointAnnotation class]])
+ {
+ static NSString *annotationIdentifier = @"NaviPointAnnotationIdentifier";
+
+ MAPinAnnotationView *pointAnnotationView = (MAPinAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier];
+ if (pointAnnotationView == nil)
+ {
+ pointAnnotationView = [[MAPinAnnotationView alloc] initWithAnnotation:annotation
+ reuseIdentifier:annotationIdentifier];
+ }
+
+ pointAnnotationView.animatesDrop = NO;
+ pointAnnotationView.canShowCallout = YES;
+ pointAnnotationView.draggable = NO;
+
+ NaviPointAnnotation *navAnnotation = (NaviPointAnnotation *)annotation;
+
+ if (navAnnotation.navPointType == NaviPointAnnotationStart)
+ {
+ [pointAnnotationView setPinColor:MAPinAnnotationColorGreen];
+ }
+ else if (navAnnotation.navPointType == NaviPointAnnotationEnd)
+ {
+ [pointAnnotationView setPinColor:MAPinAnnotationColorRed];
+ }
+
+ return pointAnnotationView;
+ }
+
+ // 处理普通点标注(排除用户定位点)
+ if ([annotation isKindOfClass:[ACustomPointAnnotation class]] &&
+ ![annotation isKindOfClass:[MAUserLocation class]])
+ {
+ static NSString *pointReuseIndentifier = @"pointReuseIndentifier";
+ ACustomAnnotationView *annotationView = (ACustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:pointReuseIndentifier];
+ if (annotationView == nil)
+ {
+ annotationView = [[ACustomAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pointReuseIndentifier];
+ }
+ else
+ {
+ // 复用时必须更新 annotation,否则可能不刷新/不显示
+ annotationView.annotation = annotation;
+ }
+// annotationView.canShowCallout= YES; //设置气泡可以弹出,默认为NO
+// annotationView.animatesDrop = NO; //设置标注动画显示,默认为NO
+// annotationView.draggable = NO; //设置标注可以拖动,默认为NO
+// annotationView.pinColor = MAPinAnnotationColorPurple;
+
+ // 设置自定义的气泡背景色
+ if (@available(iOS 14.0, *)) {
+ // iOS 14+ 可以使用 tintColor
+ annotationView.tintColor = [UIColor systemBlueColor];
+ } else {
+ // iOS 13 及以下
+ annotationView.tintColor = [UIColor colorWithRed:0.1 green:0.6 blue:0.9 alpha:1.0];
+ }
+
+ return annotationView;
+ }
+
+ return nil;
+}
+
+- (MAOverlayRenderer *)mapView:(MAMapView *)mapView rendererForOverlay:(id)overlay
+{
+ if ([overlay isKindOfClass:[SelectableOverlay class]])
+ {
+ SelectableOverlay *selectableOverlay = (SelectableOverlay *)overlay;
+
+ // SelectableOverlay 继承自 MAPolyline,直接用自身初始化 renderer
+ // renderer.overlay == overlay,避免 MAMapKit 的 overlay 不匹配警告
+ MAPolylineRenderer *polylineRenderer = [[MAPolylineRenderer alloc] initWithPolyline:selectableOverlay];
+
+ polylineRenderer.lineWidth = 8.f;
+ polylineRenderer.strokeColor = selectableOverlay.isSelected ? selectableOverlay.selectedColor : selectableOverlay.regularColor;
+
+ return polylineRenderer;
+ }
+
+ return nil;
+}
+
+// 当地图添加完标注视图后调用
+- (void)mapView:(MAMapView *)mapView didAddAnnotationViews:(NSArray *)views {
+ // 遍历所有被添加的标注视图
+# if 0
+ for (MAAnnotationView *view in views) {
+ // 检查是否为需要的标注类型,例如大头针标注
+ if ([view.annotation isMemberOfClass:[ACustomPointAnnotation class]]) {
+ // 延迟零点几秒执行,以确保视图添加动画完成(可选,但可使效果更平滑)
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ // 选中该标注,从而使其气泡弹出
+ [mapView selectAnnotation:view.annotation animated:YES];
+ });
+ }
+ }
+#endif
+}
+
+- (void)mapView:(MAMapView *)mapView didSelectAnnotationView:(MAAnnotationView *)view {
+
+ id pointAnnotation = view.annotation;
+ if ([pointAnnotation isMemberOfClass:ACustomPointAnnotation.class]) {
+ ACustomPointAnnotation *point = (ACustomPointAnnotation *)view.annotation;
+
+ NSLog(@"point: %@" , point.title);
+
+ AMapPOI * aoi = [[AMapPOI alloc] init];
+ aoi.location = [AMapGeoPoint locationWithLatitude:point.coordinate.latitude longitude:point.coordinate.longitude];
+ aoi.name = point.title;
+ aoi.address = point.subtitle;
+ aoi.uid = point.stationID;
+
+ self.dstPoi = aoi;
+
+ [self updateUIWithData:aoi textField:self.dstTf];
+
+
+ // 同步更新底部栏目的地文字
+ self.bottomBarView.destinationText = aoi.name;
+ }
+
+}
+
+// 当标注被取消选中时调用
+- (void)mapView:(MAMapView *)mapView didDeselectAnnotationView:(MAAnnotationView *)view {
+ if ([view.annotation isMemberOfClass:[ACustomPointAnnotation class]]) {
+ // 清空目的地信息 & 底部栏
+ self.dstPoi = nil;
+ self.dstTf.text = nil;
+ self.bottomBarView.destinationText = nil;
+ }
+}
+
+//选中一个点
+- (void)mapView:(MAMapView *)mapView didAnnotationViewCalloutTapped:(MAAnnotationView *)view {
+ id pointAnnotation = view.annotation;
+ if ([pointAnnotation isMemberOfClass:ACustomPointAnnotation.class]) {
+ ACustomPointAnnotation *point = (ACustomPointAnnotation *)view.annotation;
+
+
+ NSLog(@"point: %@" , point.title);
+
+ AMapPOI * aoi = [[AMapPOI alloc] init];
+
+ aoi.location = [AMapGeoPoint locationWithLatitude:point.coordinate.latitude longitude:point.coordinate.longitude];
+
+ aoi.name = point.title;
+
+ self.dstPoi = aoi;
+
+ [self updateUIWithData:aoi textField:self.dstTf];
+
+ }
+
+ NSLog(@"didSelectAnnotationView: %s" , __func__);
+
+
+}
+
+#pragma mark - AMapNaviCompositeManagerDelegate
+- (void)compositeManager:(AMapNaviCompositeManager *)compositeManager didStartNavi:(AMapNaviMode)naviMode {
+
+
+}
+
+- (void)compositeManager:(AMapNaviCompositeManager *)compositeManager onDriveStrategyChanged:(AMapNaviDrivingStrategy)driveStrategy {
+ NSLog(@"%s" , __func__ );
+
+}
+
+#pragma mark - 弹框
+-(void)willRequestTJDInfo {
+ ///调用接口获取途经点
+ ///
+
+ AMapPOI *_dstPoi = self.dstPoi ? self.dstPoi : self.defaultDstPoi;
+ if (!_dstPoi) {
+ return;
+ }
+
+ AMapGeoPoint * point = _dstPoi.location;
+ ANavPointModel *navPoint = [ANavPointModel instanceWithCoordinate:CLLocationCoordinate2DMake(point.latitude, point.longitude)
+ name:_dstPoi.name
+ address:_dstPoi.address];
+ navPoint.stationID = _dstPoi.uid;
+ self.pointModel = navPoint;
+
+ ///有_stationID 请求接口;无:不走接口,直接调整高德规划路线;
+ if (!navPoint.stationID) {
+ [self gd_calPathWithNoStationId:navPoint];
+ return;
+ }else {
+ __weak typeof(self) weakSelf = self;
+ [self requestRoutePathWithParms:nil completeHandle:^(ATripCalcDataModel *tjd) {
+ [weakSelf showDstInfoPop:navPoint];
+ }];
+
+ }
+
+}
+
+-(void)showDstInfoPop:(ANavPointModel *) navPoint {
+
+ // --- 弹出站点详情弹框 ---
+ if (!self.stationDetailPopup) {
+ AStationDetailPopupController *popup = [[AStationDetailPopupController alloc] init];
+ popup.delegate = self;
+ self.stationDetailPopup = popup;
+ }
+
+ self.stationDetailPopup.pointModel = navPoint;
+
+ ///费用
+ self.stationDetailPopup.estimatedCost = self.tjdPathInfoModel.algorithmPath.hydrogenCost;
+ ///时间
+ self.stationDetailPopup.estimatedTime = [NSString stringWithFormat:@"%.f" , self.tjdPathInfoModel.pathDto.duration / 60.0];
+
+ ///距离
+ self.stationDetailPopup.driveDistance = [NSString stringWithFormat:@"%.1f" , self.tjdPathInfoModel.pathDto.distance / 1000.0] ;
+ ///油费
+ self.stationDetailPopup.tollFee = self.tjdPathInfoModel.pathDto.tolls;
+
+ [self.stationDetailPopup presentInViewController:self];
+
+}
+
+#pragma mark - AStationDetailPopupDelegate
+
+- (void)stationDetailPopupDidTapStartNavi:(AStationDetailPopupController *)popup {
+ self.stationDetailPopup = nil;
+ ///点击开始导航:
+ ///1、有途经点,计算路线,然后再直接导航
+ ///2、没有途经点,不用计算路线,直接跳转高德规划页面,然后规划页面中点击导航;
+ ATripCalcDataModel * tjd = self.tjdPathInfoModel;
+ if (tjd && tjd.pathDto && tjd.pathDto.naviList) {
+ NSArray * arr = tjd.pathDto.naviList;
+ if ([arr isKindOfClass:NSArray.class] && arr.count > 0) {
+ [self calRoutePathWithWayPonts:arr];
+ }else {
+ [self gd_calPathWithNoStationId:self.pointModel];
+ }
+
+ }else {
+ [self gd_calPathWithNoStationId:self.pointModel];
+ }
+}
+
+- (void)stationDetailPopupDidTapClose:(AStationDetailPopupController *)popup {
+ self.stationDetailPopup = nil;
+}
+
+#pragma mark - ABottomBarViewDelegate
+
+- (void)bottomBarViewDidTapCalRoute:(ABottomBarView *)barView {
+
+ //详情弹框
+ [self willRequestTJDInfo];
+
+}
+
+- (void)bottomBarViewDidTapSearchField:(ABottomBarView *)barView {
+ // 弹出地址搜索页,选中后更新目的地输入框
+ ASearchAddressController *vc = [[ASearchAddressController alloc] init];
+ UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
+ nav.modalPresentationStyle = UIModalPresentationFullScreen;
+
+ __weak typeof(self) weakSelf = self;
+ vc.selectAddressBlk = ^(AMapPOI *poi) {
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ // 更新底部栏显示文字
+ strongSelf.bottomBarView.destinationText = poi.name;
+ // 同步旧属性(供 calRoutePath 使用)
+ poi.uid = nil;
+ [strongSelf updateUIWithData:poi textField:strongSelf.dstTf];
+ };
+
+ [self presentViewController:nav animated:YES completion:nil];
+}
+
+-(void)updateUIWithData: (AMapPOI*)poi textField: (UITextField*)tf {
+ BOOL isStart = tf.tag == 100;
+ tf.text = poi.name;
+
+ if (isStart) {
+ self.startPoi = poi;
+ }else {
+ self.dstPoi = poi;
+ }
+
+}
+
+#pragma mark - tool
+
+-(void)showAlertWithMessage:(NSString *)msg {
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:msg preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *sure = [UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+ }];
+
+ [alert addAction:sure];
+
+ [self.navigationController presentViewController:alert animated:YES completion:nil];
+
+}
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ASearchAddressController.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ASearchAddressController.h
new file mode 100644
index 0000000..0f682c9
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ASearchAddressController.h
@@ -0,0 +1,22 @@
+//
+// ASearchAddressController.h
+// ANavDemo
+//
+// Created by admin on 2026/2/6.
+//
+
+#import "ABaseViewController.h"
+
+#import
+#import
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ASearchAddressController : ABaseViewController
+
+@property (nonatomic , copy) void(^selectAddressBlk)(AMapPOI * poi);
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ASearchAddressController.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ASearchAddressController.m
new file mode 100644
index 0000000..c0ce3a8
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/ASearchAddressController.m
@@ -0,0 +1,235 @@
+//
+// ASearchAddressController.m
+// ANavDemo
+//
+// Created by admin on 2026/2/6.
+//
+
+#import "ASearchAddressController.h"
+#import "AMapNavSDKManager.h"
+
+#import
+
+#import "AMapNavCommonUtil.h"
+
+@interface ASearchAddressController ()
+
+@property (nonatomic , strong) UITableView *tableView;
+@property (nonatomic , strong) UIBarButtonItem *rightItem;
+@property (nonatomic ,strong)UIButton * backBtn;
+
+@property (nonatomic , strong) NSArray *dataArr;
+
+@property (nonatomic, strong) UITextField *inputAddressTf;
+@property (nonatomic, strong) AMapSearchAPI *search;
+@end
+
+@implementation ASearchAddressController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ // Do any additional setup after loading the view.
+ self.title = @"选择地点";
+
+ [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 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];
+}
+
+#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;
+
+ /* 搜索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 = [NSArray arrayWithArray:pois];
+ [self.tableView reloadData];
+
+}
+
+
+
+- (BOOL)textFieldShouldReturn:(UITextField *)textField {
+ [textField resignFirstResponder];
+
+ [self startSearchWithAddress:textField.text];
+
+ return YES;
+}
+
+
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.inputAddressTf resignFirstResponder];
+}
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AStationDetailPopupController.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AStationDetailPopupController.h
new file mode 100644
index 0000000..ce2a216
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AStationDetailPopupController.h
@@ -0,0 +1,55 @@
+//
+// AStationDetailPopupController.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/22.
+//
+
+#import
+#import "ANavPointModel.h"
+#import "AMapNavSDKHeader.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class AStationDetailPopupController;
+
+@protocol AStationDetailPopupDelegate
+
+@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;
+
+@property (nonatomic, weak, nullable) id delegate;
+
+/// 以半透明蒙层方式弹出在目标控制器上
+- (void)presentInViewController:(UIViewController *)parentVC;
+
+/// 关闭弹框
+- (void)dismiss;
+
+/// 关闭弹框,动画结束后执行 completion(用于关闭后再 present 其他页面)
+- (void)dismissWithCompletion:(nullable void(^)(void))completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AStationDetailPopupController.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AStationDetailPopupController.m
new file mode 100644
index 0000000..9ba560a
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Class/AStationDetailPopupController.m
@@ -0,0 +1,485 @@
+//
+// AStationDetailPopupController.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/22.
+//
+
+#import "AStationDetailPopupController.h"
+#import "ABaseViewController.h"
+#import "AMapNavCommonUtil.h"
+#import
+
+// 主题绿色(开始导航按钮背景)
+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;
+
+@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];
+}
+
+#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;
+
+ // ── 站点名称 ──
+ UILabel *nameLabel = [[UILabel alloc] init];
+ nameLabel.font = [UIFont boldSystemFontOfSize:18];
+ nameLabel.textColor = [UIColor colorWithWhite:0.1 alpha:1];
+ nameLabel.numberOfLines = 2;
+// nameLabel.adjustsFontSizeToFitWidth = YES;
+ nameLabel.minimumScaleFactor = 0.8;
+ [card addSubview:nameLabel];
+ self.stationNameLabel = nameLabel;
+
+ // ── 预计加氢费用(名称右侧,间距20pt) ──
+ UILabel *costLabel = [[UILabel alloc] init];
+ costLabel.font = [UIFont systemFontOfSize:14];
+ costLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
+ costLabel.numberOfLines = 1;
+ costLabel.textAlignment = NSTextAlignmentLeft;
+// [costLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
+// [costLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
+ [card addSubview:costLabel];
+ self.costLabel = costLabel;
+
+ // ── 地址 ──
+ UILabel *addrLabel = [[UILabel alloc] init];
+ addrLabel.font = [UIFont systemFontOfSize:13];
+ addrLabel.textColor = [UIColor colorWithWhite:0.5 alpha:1];
+ 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:@"pre_time_icon"];
+ [card addSubview:timeIcon];
+ self.timeIconView = timeIcon;
+
+ // ── 预计时间文字 ──
+ UILabel *timeLabel = [[UILabel alloc] init];
+ timeLabel.font = [UIFont systemFontOfSize:14];
+ timeLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
+ [card addSubview:timeLabel];
+ self.timeLabel = timeLabel;
+
+ // ── 行驶里程图标 ──
+ UIImageView *distIcon = [[UIImageView alloc] init];
+ distIcon.contentMode = UIViewContentModeScaleAspectFit;
+ distIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_distance_icon"];
+ [card addSubview:distIcon];
+ self.distanceIconView = distIcon;
+
+ // ── 行驶里程文字 ──
+ UILabel *distLabel = [[UILabel alloc] init];
+ distLabel.font = [UIFont systemFontOfSize:14];
+ distLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
+ [card addSubview:distLabel];
+ self.distanceLabel = distLabel;
+
+ // ── 过路费图标 ──
+ UIImageView *tollIcon = [[UIImageView alloc] init];
+ tollIcon.contentMode = UIViewContentModeScaleAspectFit;
+ tollIcon.image = [AMapNavCommonUtil imageWithName3x:@"pre_cost_icon"];
+ [card addSubview:tollIcon];
+ self.tollIconView = tollIcon;
+
+ // ── 过路费文字 ──
+ UILabel *tollLabel = [[UILabel alloc] init];
+ tollLabel.font = [UIFont systemFontOfSize:14];
+ tollLabel.textColor = [UIColor colorWithWhite:0.2 alpha:1];
+ [card addSubview:tollLabel];
+ self.tollLabel = tollLabel;
+
+ // ── 开始导航按钮 ──
+ 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.addressLabel 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);
+ }];
+
+ // ── 分割线(地址下方,12pt间距) ──
+ [self.separator mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.top.equalTo(self.addressLabel.mas_bottom).offset(15);
+ make.left.equalTo(card).offset(16);
+ make.right.equalTo(card).offset(-16);
+ make.height.mas_equalTo(0.5);
+ }];
+
+ // ── 预计时间行(分割线下方,14pt) ──
+ [self.timeIconView mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.top.equalTo(self.separator.mas_bottom).offset(15);
+ 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(30);
+ 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(15);
+ 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);
+ }];
+
+ // ── 开始导航按钮(里程行下方18pt,距卡片底部30pt) ──
+ [self.startNaviButton mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.top.equalTo(self.distanceIconView.mas_bottom).offset(50);
+ 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.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]
+ : @"过路费:--元";
+}
+
+#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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AAlgorithmPathModel.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AAlgorithmPathModel.h
new file mode 100644
index 0000000..fa7c6e0
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AAlgorithmPathModel.h
@@ -0,0 +1,67 @@
+//
+// AAlgorithmPathModel.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import
+
+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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AAlgorithmPathModel.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AAlgorithmPathModel.m
new file mode 100644
index 0000000..e328cc7
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AAlgorithmPathModel.m
@@ -0,0 +1,12 @@
+//
+// AAlgorithmPathModel.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import "AAlgorithmPathModel.h"
+
+@implementation AAlgorithmPathModel
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ACustomPointAnnotation.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ACustomPointAnnotation.h
new file mode 100644
index 0000000..8bd3f82
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ACustomPointAnnotation.h
@@ -0,0 +1,17 @@
+//
+// ACustomPointAnnotation.h
+// Pods
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ACustomPointAnnotation : MAPointAnnotation
+@property (nonatomic, copy, nullable) NSString *stationID;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ACustomPointAnnotation.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ACustomPointAnnotation.m
new file mode 100644
index 0000000..11cae88
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ACustomPointAnnotation.m
@@ -0,0 +1,12 @@
+//
+// ACustomPointAnnotation.m
+// Pods
+//
+// Created by admin on 2026/3/25.
+//
+
+#import "ACustomPointAnnotation.h"
+
+@implementation ACustomPointAnnotation
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AMapHyStationModel.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AMapHyStationModel.h
new file mode 100644
index 0000000..e02ae96
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AMapHyStationModel.h
@@ -0,0 +1,81 @@
+//
+// AMapHyStationModel.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/2/11.
+//
+
+#import
+#import
+
+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 * data;
+
+@end
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AMapHyStationModel.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AMapHyStationModel.m
new file mode 100644
index 0000000..fcf3f84
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/AMapHyStationModel.m
@@ -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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANavPointModel.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANavPointModel.h
new file mode 100644
index 0000000..d4ae31f
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANavPointModel.h
@@ -0,0 +1,26 @@
+//
+// ANavPointModel.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import
+
+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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANavPointModel.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANavPointModel.m
new file mode 100644
index 0000000..8a328a1
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANavPointModel.m
@@ -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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANaviPathInfoModel.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANaviPathInfoModel.h
new file mode 100644
index 0000000..56408a9
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANaviPathInfoModel.h
@@ -0,0 +1,32 @@
+//
+// ANaviPathInfoModel.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import
+
+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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANaviPathInfoModel.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANaviPathInfoModel.m
new file mode 100644
index 0000000..0138b10
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ANaviPathInfoModel.m
@@ -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
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/APathModel.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/APathModel.h
new file mode 100644
index 0000000..75ae288
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/APathModel.h
@@ -0,0 +1,29 @@
+//
+// APathModel.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import
+#import "ANaviPathInfoModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class ANaviPathInfoModel;
+
+@interface APathModel : NSObject
+
+@property (nonatomic, assign) CGFloat distance; // 总距离(米)
+@property (nonatomic, assign) CGFloat duration; // 总时长(秒)
+@property (nonatomic, copy, nullable) NSString *strategy; // 路线策略,如"避免拥堵"
+@property (nonatomic, copy, nullable) NSString *tolls; // 高速费(元)
+@property (nonatomic, assign) CGFloat toll_distance; // 高速里程(米)
+@property (nonatomic, assign) NSInteger restriction; // 限行标志,-1 无
+@property (nonatomic, assign) NSInteger traffic_lights; // 红绿灯数量
+@property (nonatomic, strong) NSArray *naviList; // 途经点列表
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/APathModel.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/APathModel.m
new file mode 100644
index 0000000..a9c26ca
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/APathModel.m
@@ -0,0 +1,17 @@
+//
+// APathModel.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import "APathModel.h"
+#import "ANaviPathInfoModel.h"
+
+@implementation APathModel
+
++ (NSDictionary *)mj_objectClassInArray {
+ return @{@"naviList": [ANaviPathInfoModel class]};
+}
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ASiteModel.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ASiteModel.h
new file mode 100644
index 0000000..942ec0f
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ASiteModel.h
@@ -0,0 +1,64 @@
+//
+// ASiteModel.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ {
+ "innerSiteId": "202304241822210001",
+ "name": "滨海大道加油加气站",
+ "shortName": "滨海",
+ "siteNo": "000001",
+ "city": "浙江省-嘉兴市",
+ "address": "嘉兴市平湖市滨海大道1515号",
+ "contact": "陆平",
+ "phone": "18666666666",
+ "type": "",
+ "coOpMode": "签约",
+ "booking": "无需预约",
+ "siteStatus": "0",
+ "siteStatusName": "营运中",
+ "startBusiness": "08:00:00",
+ "endBusiness": "20:00:00",
+ "billingMethod": "月付款",
+ "term": "1703952000000",
+ "remark": "",
+ "longitude": "121.07112700",
+ "latitude": "30.59577700",
+ "distance": ""
+ }
+ */
+@interface ASiteModel : NSObject
+
+@property (nonatomic, copy, nullable) NSString *innerSiteId;
+@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, copy, nullable) NSString *booking;
+@property (nonatomic, copy, nullable) NSString *siteStatus;
+@property (nonatomic, copy, nullable) NSString *siteStatusName;
+@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;
+@property (nonatomic, copy, nullable) NSString *distance;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ASiteModel.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ASiteModel.m
new file mode 100644
index 0000000..d805fbf
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ASiteModel.m
@@ -0,0 +1,12 @@
+//
+// ASiteModel.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import "ASiteModel.h"
+
+@implementation ASiteModel
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATripCalcResponse.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATripCalcResponse.h
new file mode 100644
index 0000000..c996fb2
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATripCalcResponse.h
@@ -0,0 +1,65 @@
+//
+// ATripCalcResponse.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import
+
+#import "AAlgorithmPathModel.h"
+#import "APathModel.h"
+#import "ASiteModel.h"
+#import "ANaviPathInfoModel.h"
+#import "ATruckModel.h"
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class ATruckModel;
+@class ASiteModel;
+@class APathModel;
+@class AAlgorithmPathModel;
+
+#pragma mark - data 节点
+
+/**
+ {
+ "truckDto": { ... },
+ "truckDtoStr": null,
+ "destinationSite": { ... },
+ "pathDto": { ... },
+ "algorithmPath": { ... },
+ "isInvokeAlgorithm": true
+ }
+ */
+@interface ATripCalcDataModel : NSObject
+
+@property (nonatomic, strong, nullable) ATruckModel *truckDto;
+@property (nonatomic, copy, nullable) NSString *truckDtoStr;
+@property (nonatomic, strong, nullable) ASiteModel *destinationSite;
+@property (nonatomic, strong, nullable) APathModel *pathDto;
+@property (nonatomic, strong, nullable) AAlgorithmPathModel *algorithmPath;
+@property (nonatomic, assign) BOOL isInvokeAlgorithm;
+
+@end
+
+#pragma mark - 根响应
+
+/**
+ {
+ "code": 200,
+ "msg": "操作成功!",
+ "data": { ... }
+ }
+ */
+@interface ATripCalcResponse : NSObject
+
+@property (nonatomic, assign) NSInteger code;
+@property (nonatomic, copy, nullable) NSString *msg;
+@property (nonatomic, strong, nullable) ATripCalcDataModel *data;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATripCalcResponse.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATripCalcResponse.m
new file mode 100644
index 0000000..2d0aaf7
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATripCalcResponse.m
@@ -0,0 +1,33 @@
+//
+// ATripCalcResponse.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import "ATripCalcResponse.h"
+#import "ATruckModel.h"
+#import "ASiteModel.h"
+#import "APathModel.h"
+#import "AAlgorithmPathModel.h"
+
+@implementation ATripCalcDataModel
+
++ (NSDictionary *)mj_objectClassInArray {
+ return @{
+ @"truckDto": [ATruckModel class],
+ @"destinationSite": [ASiteModel class],
+ @"pathDto": [APathModel class],
+ @"algorithmPath": [AAlgorithmPathModel class]
+ };
+}
+
+@end
+
+@implementation ATripCalcResponse
+
++ (NSDictionary *)mj_objectClassInArray {
+ return @{@"data":[ATripCalcDataModel class]};
+}
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATruckModel.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATruckModel.h
new file mode 100644
index 0000000..6c2705f
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATruckModel.h
@@ -0,0 +1,58 @@
+//
+// ATruckModel.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ {
+ "isRestriction": true,
+ "mvehicleSizeName": "重型货车",
+ "mvehicleAxisUnit": "轴",
+ "mcarNumber": "浙F32111F",
+ "mcarType": 0,
+ "mvehicleHeight": "3.8",
+ "mvehicleHeightUnit": "M",
+ "mvehicleWeight": null,
+ "mvehicleWeightUnit": "T",
+ "mvehicleLoad": "49.0",
+ "mvehicleLoadUnit": "T",
+ "mvehicleLoadSwitch": false,
+ "mvehicleWidth": "0.0",
+ "mvehicleWidthUnit": "M",
+ "mvehicleLength": "7.6",
+ "mvehicleLengthUnit": "M",
+ "mvehicleSize": 4,
+ "mvehicleAxis": 5
+ }
+ */
+@interface ATruckModel : NSObject
+
+@property (nonatomic, assign) BOOL isRestriction;
+@property (nonatomic, copy, nullable) NSString *mvehicleSizeName;
+@property (nonatomic, copy, nullable) NSString *mvehicleAxisUnit;
+@property (nonatomic, copy, nullable) NSString *mcarNumber;
+@property (nonatomic, assign) NSInteger mcarType;
+@property (nonatomic, copy, nullable) NSString *mvehicleHeight;
+@property (nonatomic, copy, nullable) NSString *mvehicleHeightUnit;
+@property (nonatomic, copy, nullable) NSString *mvehicleWeight;
+@property (nonatomic, copy, nullable) NSString *mvehicleWeightUnit;
+@property (nonatomic, copy, nullable) NSString *mvehicleLoad;
+@property (nonatomic, copy, nullable) NSString *mvehicleLoadUnit;
+@property (nonatomic, assign) BOOL mvehicleLoadSwitch;
+@property (nonatomic, copy, nullable) NSString *mvehicleWidth;
+@property (nonatomic, copy, nullable) NSString *mvehicleWidthUnit;
+@property (nonatomic, copy, nullable) NSString *mvehicleLength;
+@property (nonatomic, copy, nullable) NSString *mvehicleLengthUnit;
+@property (nonatomic, assign) NSInteger mvehicleSize;
+@property (nonatomic, assign) NSInteger mvehicleAxis;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATruckModel.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATruckModel.m
new file mode 100644
index 0000000..be75452
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Model/ATruckModel.m
@@ -0,0 +1,12 @@
+//
+// ATruckModel.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import "ATruckModel.h"
+
+@implementation ATruckModel
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavCommonUtil.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavCommonUtil.h
new file mode 100644
index 0000000..e1f7894
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavCommonUtil.h
@@ -0,0 +1,39 @@
+//
+// AMapNavCommonUtil.h
+// Pods
+//
+// Created by admin on 2026/2/11.
+//
+
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface AMapNavCommonUtil : NSObject
+
+/// 显示加载转圈(nil 消息时不显示文字)
++ (void)showLoadingWithMsg:(nullable NSString *)msg;
+
+/// 关闭所有 MBProgressHUD
++ (void)dismiss;
+
+/// 显示提示,自动 2.0s 后消失
++ (void)showMsg:(NSString *)msg;
+
+
+/// 获取图片(2x)
++ (UIImage *)imageWithName:(NSString *)name;
+
+/// 获取图片(3x)
++ (UIImage *)imageWithName3x:(NSString *)name;
+
+/// 判断字符串是否为空
+BOOL stringIsEmpty(NSString *str);
+
+/// 判断字符串是否非空
+BOOL stringIsNotEmpty(NSString *str);
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavCommonUtil.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavCommonUtil.m
new file mode 100644
index 0000000..2d34da4
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavCommonUtil.m
@@ -0,0 +1,144 @@
+//
+// AMapNavCommonUtil.m
+// Pods
+//
+// Created by admin on 2026/2/11.
+//
+
+#import "AMapNavCommonUtil.h"
+#import
+
+/// 共享 HUD 实例
+static MBProgressHUD *_sharedHUD = nil;
+
+@implementation AMapNavCommonUtil
+
+#pragma mark - MBProgressHUD
+
++ (UIWindow *)_keyWindow {
+ if (@available(iOS 13.0, *)) {
+ for (UIWindowScene *scene in [UIApplication sharedApplication].connectedScenes) {
+ if (scene.activationState == UISceneActivationStateForegroundActive) {
+ for (UIWindow *window in scene.windows) {
+ if (window.isKeyWindow) return window;
+ }
+ }
+ }
+ }
+ return [UIApplication sharedApplication].keyWindow;
+}
+
++ (void)showLoadingWithMsg:(NSString *)msg {
+ dispatch_async(dispatch_get_main_queue(), ^{
+// [self dismiss];
+
+ UIWindow *window = [self _keyWindow];
+ if (!window) return;
+
+ MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
+ hud.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
+ hud.bezelView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.75];
+ hud.contentColor = [UIColor whiteColor];
+
+ if (msg.length > 0) {
+ hud.label.text = msg;
+ hud.label.font = [UIFont systemFontOfSize:14];
+ }
+
+ hud.removeFromSuperViewOnHide = YES;
+ _sharedHUD = hud;
+ });
+}
+
++ (void)dismiss {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (_sharedHUD) {
+ [_sharedHUD hideAnimated:YES];
+ _sharedHUD = nil;
+ } else {
+ //兜底:隐藏所有 HUD(防止有遗漏)
+ UIWindow *window = [self _keyWindow];
+ if (window) {
+ [MBProgressHUD hideHUDForView:window animated:YES];
+ }
+ }
+ });
+}
+
++ (void)showMsg:(NSString *)msg {
+ if (msg.length == 0) return;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ UIWindow *window = [self _keyWindow];
+ if (!window) return;
+
+ MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:window animated:YES];
+ hud.mode = MBProgressHUDModeText;
+ hud.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
+ hud.bezelView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.75];
+ hud.contentColor = [UIColor whiteColor];
+ hud.label.text = msg;
+ hud.label.font = [UIFont systemFontOfSize:14];
+ hud.removeFromSuperViewOnHide = YES;
+
+ // 2.0s 后自动消失
+ [hud hideAnimated:YES afterDelay:2.0];
+ });
+}
+
+#pragma mark - 字符串判断
+
+
+BOOL stringIsEmpty (NSString *str)
+{
+ if (str == nil || str == NULL)
+ {
+ return YES;
+ }
+ if ([str isKindOfClass:[NSNull class]])
+ {
+ return YES;
+ }
+ if ([str isKindOfClass:[NSString class]])
+ {
+ NSString * newStr = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
+ NSSet *emptySet = [NSSet setWithObjects:@"", @"null", @"(null)", @"", @"NULL", @"无",@"kZero", nil];
+ if ([emptySet containsObject:str] || [emptySet containsObject:newStr]) {
+ return YES;
+ } else {
+ return [newStr length] == 0;
+ }
+ }
+ return NO;
+}
+
+BOOL stringIsNotEmpty (NSString *str)
+{
+ return ! stringIsEmpty(str);
+}
+
+
+#pragma mark - 获取图片
++(UIImage *)imageWithName:(NSString *)name {
+ NSURL * url = [[NSBundle mainBundle] URLForResource:@"AMapNavIOSSDK" withExtension:@"bundle"];
+ NSBundle *containnerBundle = [NSBundle bundleWithURL:url];
+
+ NSString * path = [containnerBundle pathForResource:[NSString stringWithFormat:@"%@@2x.png" , name] ofType:nil];
+
+ UIImage * arrowImage = [[UIImage imageWithContentsOfFile:path] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
+
+ return arrowImage;
+}
+
++(UIImage *)imageWithName3x:(NSString *)name {
+ NSURL * url = [[NSBundle mainBundle] URLForResource:@"AMapNavIOSSDK" withExtension:@"bundle"];
+ NSBundle *containnerBundle = [NSBundle bundleWithURL:url];
+
+ NSString * path = [containnerBundle pathForResource:[NSString stringWithFormat:@"%@@3x.png" , name] ofType:nil];
+
+ UIImage * arrowImage = [[UIImage imageWithContentsOfFile:path] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
+
+ return arrowImage;
+}
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavHttpUtil.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavHttpUtil.h
new file mode 100644
index 0000000..4aef2b2
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavHttpUtil.h
@@ -0,0 +1,19 @@
+//
+// AMapNavHttpUtil.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/2/11.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface AMapNavHttpUtil : NSObject
+
++ (void)postRequestWithURL:(NSString *)urlString parameters:(id)parameters requestHeader:(NSDictionary *)headParam successHandler:(void (^)(NSDictionary *data, NSURLResponse *response))successHandler failureHandler:(void ( ^)(NSError *error))failureHandler;
+
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavHttpUtil.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavHttpUtil.m
new file mode 100644
index 0000000..dd80c43
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapNavHttpUtil.m
@@ -0,0 +1,114 @@
+//
+// AMapNavHttpUtil.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/2/11.
+//
+
+#import "AMapNavHttpUtil.h"
+
+#define AMapRequestMethod_POST @"POST"
+
+@interface AMapNavHttpUtil ()
+@property (nonatomic , copy) NSString * baseURL;
+@end
+
+@implementation AMapNavHttpUtil
+
++ (instancetype)sharedInstance {
+ static AMapNavHttpUtil *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+
+ // dispatch_once确保下面的代码块只被执行一次
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [[self alloc] init];
+ // 可以在这里进行一些初始化操作
+
+ });
+ return sharedInstance;
+}
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+
+// if (sdk.config.developmentModel) {
+// _baseURL = kYTOTPAnalyticsSDKTestHost;
+// }else {
+// _baseURL = kYTOTPAnalyticsSDKProductionHost;
+// }
+ }
+
+ return self;
+}
+
+
++ (void)postRequestWithURL:(NSString *)urlString parameters:(id)parameters requestHeader:(NSDictionary *)headParam successHandler:(void (^)(NSDictionary *data, NSURLResponse *response))successHandler failureHandler:(void ( ^)(NSError *error))failureHandler {
+ [self requestWithMethod:AMapRequestMethod_POST URL:urlString parameters:parameters requestHeader:headParam successHandler:successHandler failureHandler:failureHandler];
+}
+
+
+// 使用NSURLSession发起网络请求
++ (void)requestWithMethod:(NSString *)method URL:(NSString *)urlString parameters:(id)parameters requestHeader:(NSDictionary *)headParam successHandler:(void (^)(NSDictionary *data, NSURLResponse *response))successHandler failureHandler:(void (^)(NSError *error))failureHandler {
+ if (!urlString) {
+ return;
+ }
+
+ // 创建URL
+ NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@" , urlString]];
+
+ // 创建请求
+ NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
+ request.HTTPMethod = method;
+ request.timeoutInterval = 30.0;
+
+ // 设置请求头,指定数据通讯格式为json
+ if (headParam) {
+ for (NSString *key in headParam.allKeys) {
+ if (headParam[key]) {
+ [request setValue:[NSString stringWithFormat:@"%@",headParam[key]] forHTTPHeaderField:key];
+ }
+ }
+ }
+
+ // 将参数转换为JSON数据
+ NSError *error;
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:&error];
+ if (!jsonData) {}
+
+ if ([method isEqualToString: AMapRequestMethod_POST]) {
+ // 设置请求体
+ request.HTTPBody = jsonData;
+ }
+
+ __block NSURLSession *session = [NSURLSession sharedSession];
+ NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
+ if (error) {
+ NSLog(@"request error:%@" , error);
+ if (failureHandler) {
+ failureHandler(error);
+ }
+ } else {
+ if (successHandler) {
+ NSDictionary * dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
+ if(dic){
+ NSLog(@"url: %@ , response data:%@", url , dic);
+ }
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ successHandler(dic, response);
+ });
+// successHandler(dic, response);
+ }
+ }
+
+ [session finishTasksAndInvalidate];
+ session = nil;
+ }];
+
+ // 开始任务
+ [task resume];
+}
+
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapPrivacyUtility.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapPrivacyUtility.h
new file mode 100644
index 0000000..c9f7472
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapPrivacyUtility.h
@@ -0,0 +1,32 @@
+//
+// AMapPrivacyUtility.h
+// officialDemoNavi
+//
+// Created by menglong on 2021/10/29.
+// Copyright © 2021 AutoNavi. All rights reserved.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+/*
+ * 隐私合规使用demo 工具类
+ */
+@interface AMapPrivacyUtility : NSObject
+
+/**
+ * @brief 通过这个方法来判断是否同意隐私合规
+ * 1.如果没有同意隐私合规,则创建的SDK manager 实例返回 为nil, 无法使用SDK提供的功能
+ * 2.如果同意了下次启动不提示 的授权,则不会弹框给用户
+ * 3.如果只同意了,则下次启动还要给用户弹框提示
+ */
+
++ (void)handlePrivacyAgreeStatus;
+
++ (void)handlePrivacyAgreeStatusIn:(UIViewController*)targetVC;
+
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapPrivacyUtility.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapPrivacyUtility.m
new file mode 100644
index 0000000..3e96c97
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/AMapPrivacyUtility.m
@@ -0,0 +1,137 @@
+//
+// AMapPrivacyUtility.m
+// officialDemoNavi
+//
+// Created by menglong on 2021/10/29.
+// Copyright © 2021 AutoNavi. All rights reserved.
+//
+
+#import "AMapPrivacyUtility.h"
+#import
+#import
+@implementation AMapPrivacyUtility
+
++ (void)showPrivacyInfoInWindow:(UIWindow *)window {
+
+ NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
+
+ paragraphStyle.alignment = NSTextAlignmentLeft;
+
+ NSMutableAttributedString *privacyInfo = [[NSMutableAttributedString alloc] initWithString:@"\n亲,感谢您对XXX一直以来的信任!我们依据最新的监管要求更新了XXX《隐私权政策》,特向您说明如下\n1.为向您提供交易相关基本功能,我们会收集、使用必要的信息;\n2.基于您的明示授权,我们可能会获取您的位置(为您提供附近的商品、店铺及优惠资讯等)等信息,您有权拒绝或取消授权;\n3.我们会采取业界先进的安全措施保护您的信息安全;\n4.未经您同意,我们不会从第三方处获取、共享或向提供您的信息;" attributes:@{
+ NSParagraphStyleAttributeName:paragraphStyle,
+ }];
+
+ [privacyInfo addAttribute:NSLinkAttributeName
+ value:@"《隐私权政策》"
+ range:[[privacyInfo string] rangeOfString:@"《隐私权政策》"]];
+
+ UIAlertController *privacyInfoController = [UIAlertController alertControllerWithTitle:@"温馨提示(隐私合规示例)" message:@"" preferredStyle:UIAlertControllerStyleAlert];
+
+ [privacyInfoController setValue:privacyInfo forKey:@"attributedMessage"];
+
+
+ UIAlertAction *agreeAllAction = [UIAlertAction actionWithTitle:@"同意(下次不提示)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"agreeStatus"];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+ //更新用户授权高德SDK隐私协议状态. since 8.1.0
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
+ }];
+
+
+ UIAlertAction *agreeAction = [UIAlertAction actionWithTitle:@"同意" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+ //更新用户授权高德SDK隐私协议状态. since 8.1.0
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
+ }];
+
+ UIAlertAction *notAgreeAction = [UIAlertAction actionWithTitle:@"不同意" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"agreeStatus"];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+ //更新用户授权高德SDK隐私协议状态. since 8.1.0
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusNotAgree];
+ }];
+
+ [privacyInfoController addAction:agreeAllAction];
+ [privacyInfoController addAction:agreeAction];
+ [privacyInfoController addAction:notAgreeAction];
+
+ [window.rootViewController presentViewController:privacyInfoController animated:YES completion:^{
+ //更新App是否显示隐私弹窗的状态,隐私弹窗是否包含高德SDK隐私协议内容的状态. since 8.1.0
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyShow:AMapPrivacyShowStatusDidShow privacyInfo:AMapPrivacyInfoStatusDidContain];
+ }];
+
+}
+
++ (void)handlePrivacyAgreeStatus {
+ //判断是否同意了隐私协议下次不提示
+// if(![[NSUserDefaults standardUserDefaults] boolForKey:@"agreeStatus"]){
+ //添加隐私合规弹窗
+ [self showPrivacyInfoInWindow:[UIApplication sharedApplication].delegate.window];
+
+// [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
+// }
+}
+
++ (void)handlePrivacyAgreeStatusIn:(UIViewController*)targetVC {
+ if(![[NSUserDefaults standardUserDefaults] boolForKey:@"agreeStatus"]){
+ [self showPrivacyInfoInWindowWithVC:targetVC];
+ }
+}
+
++ (void)showPrivacyInfoInWindowWithVC:(UIViewController *)window {
+
+ NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
+
+ paragraphStyle.alignment = NSTextAlignmentLeft;
+
+ NSMutableAttributedString *privacyInfo = [[NSMutableAttributedString alloc] initWithString:@"\n感谢您一直以来的信任!我们依据最新的监管要求更新了《隐私权政策》,特向您说明如下\n1.为向您提供交易相关基本功能,我们会收集、使用必要的信息;\n2.基于您的明示授权,我们可能会获取您的位置(为您提供附近的店铺及优惠资讯等)等信息,您有权拒绝或取消授权;\n3.我们会采取业界先进的安全措施保护您的信息安全;\n4.未经您同意,我们不会从第三方处获取、共享或向提供您的信息;" attributes:@{
+ NSParagraphStyleAttributeName:paragraphStyle,
+ }];
+
+ [privacyInfo addAttribute:NSLinkAttributeName
+ value:@"《隐私权政策》"
+ range:[[privacyInfo string] rangeOfString:@"《隐私权政策》"]];
+
+ UIAlertController *privacyInfoController = [UIAlertController alertControllerWithTitle:@"温馨提示(隐私合规示例)" message:@"" preferredStyle:UIAlertControllerStyleAlert];
+
+ [privacyInfoController setValue:privacyInfo forKey:@"attributedMessage"];
+
+
+ UIAlertAction *agreeAllAction = [UIAlertAction actionWithTitle:@"同意(下次不提示)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"agreeStatus"];
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"usragreeStatus"];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+ //更新用户授权高德SDK隐私协议状态. since 8.1.0
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
+
+ [NSNotificationCenter.defaultCenter postNotificationName:@"ksAMapPrivacyDidUpdateNotification" object:nil];
+ }];
+
+
+ UIAlertAction *agreeAction = [UIAlertAction actionWithTitle:@"同意" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
+ //更新用户授权高德SDK隐私协议状态. since 8.1.0
+ [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"usragreeStatus"];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusDidAgree];
+ [NSNotificationCenter.defaultCenter postNotificationName:@"ksAMapPrivacyDidUpdateNotification" object:nil];
+ }];
+
+ UIAlertAction *notAgreeAction = [UIAlertAction actionWithTitle:@"不同意" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"agreeStatus"];
+ [[NSUserDefaults standardUserDefaults] synchronize];
+ //更新用户授权高德SDK隐私协议状态. since 8.1.0
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyAgree:AMapPrivacyAgreeStatusNotAgree];
+ }];
+
+ [privacyInfoController addAction:agreeAllAction];
+ [privacyInfoController addAction:agreeAction];
+ [privacyInfoController addAction:notAgreeAction];
+
+ [window presentViewController:privacyInfoController animated:YES completion:^{
+ //更新App是否显示隐私弹窗的状态,隐私弹窗是否包含高德SDK隐私协议内容的状态. since 8.1.0
+ [[AMapNaviManagerConfig sharedConfig] updatePrivacyShow:AMapPrivacyShowStatusDidShow privacyInfo:AMapPrivacyInfoStatusDidContain];
+ }];
+
+}
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/NaviPointAnnotation.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/NaviPointAnnotation.h
new file mode 100644
index 0000000..a488be4
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/NaviPointAnnotation.h
@@ -0,0 +1,22 @@
+//
+// NaviPointAnnotation.h
+// AMapNaviKit
+//
+// Created by 刘博 on 16/3/8.
+// Copyright © 2016年 AutoNavi. All rights reserved.
+//
+
+#import
+
+typedef NS_ENUM(NSInteger, NaviPointAnnotationType)
+{
+ NaviPointAnnotationStart,
+ NaviPointAnnotationWay,
+ NaviPointAnnotationEnd
+};
+
+@interface NaviPointAnnotation : MAPointAnnotation
+
+@property (nonatomic, assign) NaviPointAnnotationType navPointType;
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/NaviPointAnnotation.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/NaviPointAnnotation.m
new file mode 100644
index 0000000..81808f5
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/NaviPointAnnotation.m
@@ -0,0 +1,13 @@
+//
+// NaviPointAnnotation.m
+// AMapNaviKit
+//
+// Created by 刘博 on 16/3/8.
+// Copyright © 2016年 AutoNavi. All rights reserved.
+//
+
+#import "NaviPointAnnotation.h"
+
+@implementation NaviPointAnnotation
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.h
new file mode 100755
index 0000000..3b631ad
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.h
@@ -0,0 +1,24 @@
+//
+// SelectableOverlay.h
+// officialDemo2D
+//
+// Created by yi chen on 14-5-8.
+// Copyright (c) 2014年 AutoNavi. All rights reserved.
+//
+
+#import
+#import
+
+/// 继承 MAPolyline,自身就是 Polyline,renderer 用 self 初始化不会产生 overlay 不匹配警告
+@interface SelectableOverlay : MAPolyline
+
+@property (nonatomic, assign) NSInteger routeID;
+
+@property (nonatomic, assign, getter = isSelected) BOOL selected;
+@property (nonatomic, strong) UIColor *selectedColor;
+@property (nonatomic, strong) UIColor *regularColor;
+
+/// 用坐标数组和数量初始化(对应原来的 MAPolyline polylineWithCoordinates:count:)
++ (instancetype)overlayWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count;
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.m
new file mode 100755
index 0000000..a8d0789
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/Tools/SelectableOverlay.m
@@ -0,0 +1,23 @@
+//
+// SelectableOverlay.m
+// officialDemo2D
+//
+// Created by yi chen on 14-5-8.
+// Copyright (c) 2014年 AutoNavi. All rights reserved.
+//
+
+#import "SelectableOverlay.h"
+
+@implementation SelectableOverlay
+
++ (instancetype)overlayWithCoordinates:(CLLocationCoordinate2D *)coords count:(NSUInteger)count
+{
+ // MAPolyline 的指定工厂方法,返回 SelectableOverlay 实例
+ SelectableOverlay *overlay = (SelectableOverlay *)[super polylineWithCoordinates:coords count:count];
+ overlay.selected = NO;
+ overlay.selectedColor = [UIColor colorWithRed:0.05 green:0.39 blue:0.9 alpha:0.8];
+ overlay.regularColor = [UIColor colorWithRed:0.5 green:0.6 blue:0.9 alpha:0.8];
+ return overlay;
+}
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ABottomBarView.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ABottomBarView.h
new file mode 100644
index 0000000..dede542
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ABottomBarView.h
@@ -0,0 +1,32 @@
+//
+// ABottomBarView.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import
+#import "AMapNavSDKHeader.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class ABottomBarView;
+
+@protocol ABottomBarViewDelegate
+/// 点击「规划路线」按钮
+- (void)bottomBarViewDidTapCalRoute:(ABottomBarView *)barView;
+/// 输入框开始编辑(外部弹起搜索页)
+- (void)bottomBarViewDidTapSearchField:(ABottomBarView *)barView;
+@end
+
+/// 底部搜索+规划路线栏
+@interface ABottomBarView : UIView
+
+@property (nonatomic, weak) id delegate;
+
+/// 目的地文本(外部赋值后自动更新输入框)
+@property (nonatomic, copy, nullable) NSString *destinationText;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ABottomBarView.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ABottomBarView.m
new file mode 100644
index 0000000..047e079
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ABottomBarView.m
@@ -0,0 +1,204 @@
+//
+// ABottomBarView.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/25.
+//
+
+#import "ABottomBarView.h"
+#import "AMapNavCommonUtil.h"
+#import
+
+// 主题绿
+static inline UIColor *ABottomBarThemeGreen(void) {
+ return [UIColor colorWithRed:0x1A/255.0 green:0x6E/255.0 blue:0x45/255.0 alpha:1.0];
+}
+
+@interface ABottomBarView ()
+
+/// 白色圆角背景卡片
+@property (nonatomic, strong) UIView *cardView;
+
+/// 搜索图标
+@property (nonatomic, strong) UIImageView *searchIconView;
+
+/// 目的地输入框
+@property (nonatomic, strong) UITextField *searchField;
+
+/// 规划路线按钮
+@property (nonatomic, strong) UIButton *calRouteButton;
+
+@end
+
+@implementation ABottomBarView
+
+#pragma mark - Init
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+ if (self) {
+ self.backgroundColor = [UIColor clearColor];
+ [self _buildUI];
+ }
+ return self;
+}
+
+- (instancetype)init {
+ return [self initWithFrame:CGRectZero];
+}
+
+#pragma mark - Build UI
+
+- (void)_buildUI {
+ // ── 背景卡片 ──────────────────────────────────────────
+ UIView *card = [[UIView alloc] init];
+// card.backgroundColor = [UIColor colorWithRed:0.96 green:0.97 blue:0.98 alpha:0.96];
+ card.backgroundColor = [UIColor whiteColor];
+ card.layer.cornerRadius = 16;
+ // 顶部阴影
+ card.layer.shadowColor = [UIColor blackColor].CGColor;
+ card.layer.shadowOpacity = 0.10;
+ card.layer.shadowRadius = 10;
+ card.layer.shadowOffset = CGSizeMake(0, -3);
+ card.layer.masksToBounds = NO;
+ [self addSubview:card];
+ self.cardView = card;
+
+ [card mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.edges.equalTo(self);
+ }];
+
+ // ── 搜索框容器(白色圆角行) ──────────────────────────
+ UIView *searchRow = [[UIView alloc] init];
+ searchRow.backgroundColor = [UIColor whiteColor];
+ searchRow.layer.cornerRadius = 5;
+ searchRow.layer.masksToBounds = YES;
+ searchRow.layer.borderColor = [UIColor colorWithRed:0.95 green:0.95 blue:0.95 alpha:1].CGColor;
+ searchRow.layer.borderWidth = 1;
+ [card addSubview:searchRow];
+
+ [searchRow mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.top.equalTo(card).offset(-18);
+ make.left.equalTo(card).offset(16);
+ make.right.equalTo(card).offset(-16);
+ make.height.mas_equalTo(50);
+ }];
+
+ // ── 搜索图标 ──────────────────────────────────────────
+ UIImageView *searchIcon = [[UIImageView alloc] init];
+ searchIcon.contentMode = UIViewContentModeScaleAspectFit;
+ searchIcon.image = [AMapNavCommonUtil imageWithName3x:@"search_icon"];
+ [searchRow addSubview:searchIcon];
+ self.searchIconView = searchIcon;
+
+ [searchIcon mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.centerY.equalTo(searchRow);
+ make.left.equalTo(searchRow).offset(12);
+ make.width.height.mas_equalTo(18);
+ }];
+
+ // ── 目的地输入框 ──────────────────────────────────────
+ UITextField *field = [[UITextField alloc] init];
+ field.placeholder = @"请输入目的地,不输入则自动匹配附近成本最低加氢站";
+ field.font = [UIFont systemFontOfSize:14];
+ field.textColor = [UIColor colorWithWhite:0.1 alpha:1];
+ field.borderStyle = UITextBorderStyleNone;
+ field.backgroundColor = [UIColor clearColor];
+ field.delegate = self;
+ // placeholder 颜色
+ if (field.placeholder) {
+ field.attributedPlaceholder = [[NSAttributedString alloc]
+ initWithString:field.placeholder
+ attributes:@{NSForegroundColorAttributeName:
+ [UIColor colorWithWhite:0.65 alpha:1],
+ NSFontAttributeName:
+ [UIFont systemFontOfSize:13]}];
+ }
+ [searchRow addSubview:field];
+ self.searchField = field;
+
+ [field mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.centerY.equalTo(searchRow);
+ make.left.equalTo(searchIcon.mas_right).offset(8);
+ make.right.equalTo(searchRow).offset(-12);
+ make.top.bottom.equalTo(searchRow);
+ }];
+
+ // ── 规划路线按钮 ──────────────────────────────────────
+ UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
+ btn.backgroundColor = ABottomBarThemeGreen();
+ btn.layer.cornerRadius = 24;
+ btn.layer.masksToBounds = YES;
+
+ // 左侧图标
+ UIImageView *routeIcon = [[UIImageView alloc] init];
+ routeIcon.contentMode = UIViewContentModeScaleAspectFit;
+ routeIcon.image = [AMapNavCommonUtil imageWithName3x:@"cal_ruoute_icon"];
+ routeIcon.userInteractionEnabled = NO;
+ [btn addSubview:routeIcon];
+
+ // 标题
+ UILabel *titleLbl = [[UILabel alloc] init];
+ titleLbl.text = @"规划路线";
+ titleLbl.textColor = [UIColor whiteColor];
+ titleLbl.font = [UIFont boldSystemFontOfSize:16];
+ titleLbl.userInteractionEnabled = NO;
+ [btn addSubview:titleLbl];
+
+ // 图标和文字水平居中整体
+ [routeIcon mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.centerY.equalTo(btn);
+ make.right.equalTo(titleLbl.mas_left).offset(-8);
+ make.width.height.mas_equalTo(22);
+ }];
+
+ [titleLbl mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.centerY.equalTo(btn);
+ // 两者整体水平居中:titleLbl 向右偏移 (22+8)/2 = 15pt
+ make.centerX.equalTo(btn).offset(15);
+ }];
+
+ [btn addTarget:self action:@selector(_onCalRouteTapped) forControlEvents:UIControlEventTouchUpInside];
+ [card addSubview:btn];
+ self.calRouteButton = btn;
+
+ CGFloat off_y = AMP_TabbarHeight;
+#ifdef kAMapSDKDebugFlag
+ off_y = 0;
+#endif
+
+ [btn mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.top.equalTo(searchRow.mas_bottom).offset(20);
+ make.left.equalTo(card).offset(16);
+ make.right.equalTo(card).offset(-16);
+ make.height.mas_equalTo(48);
+ make.bottom.equalTo(card).offset(-40 - off_y);
+ }];
+}
+
+#pragma mark - Public
+
+- (void)setDestinationText:(NSString *)destinationText {
+ _destinationText = destinationText;
+ self.searchField.text = destinationText;
+}
+
+#pragma mark - Actions
+
+- (void)_onCalRouteTapped {
+ if ([self.delegate respondsToSelector:@selector(bottomBarViewDidTapCalRoute:)]) {
+ [self.delegate bottomBarViewDidTapCalRoute:self];
+ }
+}
+
+#pragma mark - UITextFieldDelegate
+
+- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
+ // 不弹起键盘,直接通知外部展示搜索页
+ if ([self.delegate respondsToSelector:@selector(bottomBarViewDidTapSearchField:)]) {
+ [self.delegate bottomBarViewDidTapSearchField:self];
+ }
+ return NO;
+}
+
+@end
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ACustomStepView.h b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ACustomStepView.h
new file mode 100644
index 0000000..b1d3a8d
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ACustomStepView.h
@@ -0,0 +1,21 @@
+//
+// ACustomStepView.h
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/11.
+//
+
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ACustomStepView : UIView
+
+@property (nonatomic, readonly , assign) CGFloat value; // 当前值
+
+
+- (instancetype)initWithValue:(CGFloat)currentValue maxValue:(CGFloat)maxValue min:(CGFloat)minValue;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ACustomStepView.m b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ACustomStepView.m
new file mode 100644
index 0000000..0625ec6
--- /dev/null
+++ b/ln_jq_app/ios/AMapNavIOSSDK/AMapNavIOSSDK/Classes/View/ACustomStepView.m
@@ -0,0 +1,121 @@
+//
+// ACustomStepView.m
+// AMapNavIOSSDK
+//
+// Created by admin on 2026/3/11.
+//
+
+#import "ACustomStepView.h"
+#import
+
+#define kStepValue 0.5
+
+@interface ACustomStepView ()
+@property CGFloat value;
+@property CGFloat maxValue;
+@property CGFloat minValue;
+@end
+
+@implementation ACustomStepView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+ if (self) {
+ self.backgroundColor = [UIColor whiteColor];
+ self.layer.cornerRadius = 4;
+ self.layer.masksToBounds = YES;
+ self.clipsToBounds = YES;
+ self.layer.borderColor = [UIColor colorWithRed:225/255.0 green:225/255.0 blue:225/255.0 alpha:1].CGColor;
+ self.layer.borderWidth = 1;
+
+ [self setupSubviews];
+
+ self.value = 0; // 初始值
+ }
+ return self;
+}
+
+- (instancetype)initWithValue:(CGFloat)currentValue maxValue:(CGFloat)maxValue min:(CGFloat)minValue {
+// self = [super initWithFrame:CGRectZero];
+
+ if (self) {
+ _value = currentValue;
+ _maxValue = maxValue;
+ _minValue = minValue;
+ }
+
+
+ return self;
+}
+
+- (void)setupSubviews {
+ // 减按钮
+ UIButton *minusButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ [minusButton setTitle:@"-" forState:UIControlStateNormal];
+ minusButton.titleLabel.font = [UIFont boldSystemFontOfSize:20];
+ [minusButton addTarget:self action:@selector(decrement:) forControlEvents:UIControlEventTouchUpInside];
+ [minusButton setTintColor:[UIColor colorWithRed:0x35/255.0 green:0x35/255.0 blue:0x35/255.0 alpha:1]];
+ [self addSubview:minusButton];
+
+ // 加按钮
+ UIButton *plusButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ [plusButton setTitle:@"+" forState:UIControlStateNormal];
+ plusButton.titleLabel.font = [UIFont boldSystemFontOfSize:20];
+ [plusButton setTintColor:[UIColor colorWithRed:0x35/255.0 green:0x35/255.0 blue:0x35/255.0 alpha:1]];
+
+ [plusButton addTarget:self action:@selector(increment:) forControlEvents:UIControlEventTouchUpInside];
+ [self addSubview:plusButton];
+
+ [plusButton mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.left.top.right.equalTo(self);
+ make.height.equalTo(@40);
+ }];
+
+ UIView * line = [[UIView alloc] init];
+ line.backgroundColor = [UIColor colorWithRed:230/255.0 green:230/255.0 blue:230/255.0 alpha:1];
+ [self addSubview:line];
+ [line mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.left.right.equalTo(self);
+ make.top.equalTo(plusButton.mas_bottom);
+ make.height.equalTo(@1);
+ }];
+
+
+ [minusButton mas_makeConstraints:^(MASConstraintMaker *make) {
+ make.left.right.equalTo(self);
+ make.top.equalTo(plusButton.mas_bottom);
+ make.height.equalTo(plusButton);
+ }];
+
+
+}
+
+// 减1操作
+- (void)decrement:(UIButton *)sender {
+ if (self.value <= self.minValue) {
+ return;
+ }
+ if (self.value - kStepValue < self.minValue) {
+ self.value = self.minValue;
+ return;
+ }
+
+ self.value = self.value - kStepValue;
+}
+
+// 加1操作
+- (void)increment:(UIButton *)sender {
+ if (self.value >= self.maxValue) {
+ return;
+ }
+ if (self.value + kStepValue > self.maxValue) {
+ self.value = self.maxValue;
+ return;
+ }
+
+ self.value = self.value + kStepValue;
+
+}
+
+
+@end
diff --git a/ln_jq_app/ios/Podfile b/ln_jq_app/ios/Podfile
index 456cd48..3cfdd14 100644
--- a/ln_jq_app/ios/Podfile
+++ b/ln_jq_app/ios/Podfile
@@ -31,8 +31,14 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
flutter_ios_podfile_setup
target 'Runner' do
- use_frameworks!
+# use_frameworks!
+use_frameworks! :linkage => :static
+ pod 'AMapNavIOSSDK' , :path => './AMapNavIOSSDK'
+ ## 本地仓库
+# pod 'AMapNavIOSSDK' , :path => '../../../../demo/ANavDemo'
+
+
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
diff --git a/ln_jq_app/ios/Podfile.lock b/ln_jq_app/ios/Podfile.lock
index a09f840..423a1d3 100644
--- a/ln_jq_app/ios/Podfile.lock
+++ b/ln_jq_app/ios/Podfile.lock
@@ -1,12 +1,26 @@
PODS:
- AlicloudELS (1.0.3)
- AlicloudPush (3.2.3):
- - AlicloudELS (= 1.0.3)
+ - AlicloudELS (~> 1.0.3)
- AlicloudUTDID (~> 1.0)
- AlicloudUTDID (1.6.1)
- aliyun_push_flutter (0.0.1):
- AlicloudPush (< 4.0, >= 3.2.3)
- Flutter
+ - AMapFoundation-NO-IDFA (1.8.2)
+ - AMapLocation-NO-IDFA (2.11.0):
+ - AMapFoundation-NO-IDFA (>= 1.8.0)
+ - AMapNavi-NO-IDFA (10.1.600):
+ - AMapFoundation-NO-IDFA (>= 1.8.2)
+ - AMapNavIOSSDK (0.1.0):
+ - AMapLocation-NO-IDFA
+ - AMapNavi-NO-IDFA
+ - AMapSearch-NO-IDFA
+ - Masonry
+ - MBProgressHUD
+ - MJExtension
+ - AMapSearch-NO-IDFA (9.7.4):
+ - AMapFoundation-NO-IDFA (>= 1.8.0)
- connectivity_plus (0.0.1):
- Flutter
- device_info_plus (0.0.1):
@@ -30,6 +44,9 @@ PODS:
- FlutterMacOS
- image_picker_ios (0.0.1):
- Flutter
+ - Masonry (1.1.0)
+ - MBProgressHUD (1.2.0)
+ - MJExtension (3.4.2)
- mobile_scanner (7.0.0):
- Flutter
- FlutterMacOS
@@ -51,6 +68,7 @@ PODS:
DEPENDENCIES:
- aliyun_push_flutter (from `.symlinks/plugins/aliyun_push_flutter/ios`)
+ - AMapNavIOSSDK (from `./AMapNavIOSSDK`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
@@ -75,11 +93,20 @@ SPEC REPOS:
- AlicloudELS
- AlicloudPush
trunk:
+ - AMapFoundation-NO-IDFA
+ - AMapLocation-NO-IDFA
+ - AMapNavi-NO-IDFA
+ - AMapSearch-NO-IDFA
+ - Masonry
+ - MBProgressHUD
+ - MJExtension
- OrderedSet
EXTERNAL SOURCES:
aliyun_push_flutter:
:path: ".symlinks/plugins/aliyun_push_flutter/ios"
+ AMapNavIOSSDK:
+ :path: "./AMapNavIOSSDK"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
@@ -115,9 +142,14 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
AlicloudELS: fbf821383330465a5af84a033f36f263ae46ca41
- AlicloudPush: 95150880af380f64cf1741f5586047c17d36c1d9
+ AlicloudPush: 52cbf38ffc20c07f039cbc72d5738745fd986215
AlicloudUTDID: 5d2f22d50e11eecd38f30bc7a48c71925ea90976
aliyun_push_flutter: 0fc2f048a08687ef256c0cfdd72dd7a550ef3347
+ AMapFoundation-NO-IDFA: 6ce0ef596d4eb8d934ff498e56747b6de1247b05
+ AMapLocation-NO-IDFA: 590fd42af0c8ea9eac26978348221bbc16be4ef9
+ AMapNavi-NO-IDFA: 22edfa7d6a81d75c91756e31b6c26b7746152233
+ AMapNavIOSSDK: e06adcb48ac8abeace46ea31f72191564e54a186
+ AMapSearch-NO-IDFA: 53b2193244be8f07f3be0a4d5161200236960587
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
@@ -127,6 +159,9 @@ SPEC CHECKSUMS:
flutter_pdfview: 32bf27bda6fd85b9dd2c09628a824df5081246cf
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
+ Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
+ MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
+ MJExtension: e97d164cb411aa9795cf576093a1fa208b4a8dd8
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
@@ -136,6 +171,6 @@ SPEC CHECKSUMS:
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
-PODFILE CHECKSUM: 357c01ff4e7591871e8c4fd6462220a8c7447220
+PODFILE CHECKSUM: b4931d4490f04261e0fda802d44e275ab3619244
COCOAPODS: 1.16.2
diff --git a/ln_jq_app/ios/Runner.xcodeproj/project.pbxproj b/ln_jq_app/ios/Runner.xcodeproj/project.pbxproj
index 1a103b2..6ed39fa 100644
--- a/ln_jq_app/ios/Runner.xcodeproj/project.pbxproj
+++ b/ln_jq_app/ios/Runner.xcodeproj/project.pbxproj
@@ -8,11 +8,12 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 307490676CE2A16C8D75B103 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 937F9432963895EF63BCCD38 /* Pods_RunnerTests.framework */; };
+ 298D3D45379E4332D4A8A627 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 95135D36941D5EF2C00065B2 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 59E555C098DB12132BCE9F6E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6AF04C5CFFF0B4098EEDA799 /* Pods_Runner.framework */; };
+ 3F21125D6B84D3CC58F3C574 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85810788944AB2417549F45E /* Pods_RunnerTests.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 8420D0082F3D9F7E006DB6CC /* NativeFirstPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8420D0072F3D9F7E006DB6CC /* NativeFirstPage.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -49,13 +50,14 @@
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
4B58A54CFC9A912F2BA04FF2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
- 6AF04C5CFFF0B4098EEDA799 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6D3F89E22F04C32900A154AD /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 8420D0072F3D9F7E006DB6CC /* NativeFirstPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeFirstPage.swift; sourceTree = ""; };
+ 85810788944AB2417549F45E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
87773E6EB1B2C64DA1B1FA42 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
- 937F9432963895EF63BCCD38 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 95135D36941D5EF2C00065B2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -73,7 +75,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 59E555C098DB12132BCE9F6E /* Pods_Runner.framework in Frameworks */,
+ 298D3D45379E4332D4A8A627 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -81,7 +83,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 307490676CE2A16C8D75B103 /* Pods_RunnerTests.framework in Frameworks */,
+ 3F21125D6B84D3CC58F3C574 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -152,6 +154,7 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 8420D0072F3D9F7E006DB6CC /* NativeFirstPage.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
@@ -160,8 +163,8 @@
E621C70ABD0685462494972D /* Frameworks */ = {
isa = PBXGroup;
children = (
- 6AF04C5CFFF0B4098EEDA799 /* Pods_Runner.framework */,
- 937F9432963895EF63BCCD38 /* Pods_RunnerTests.framework */,
+ 95135D36941D5EF2C00065B2 /* Pods_Runner.framework */,
+ 85810788944AB2417549F45E /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "";
@@ -199,7 +202,6 @@
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 590CF992B35CC61AF9AA4341 /* [CP] Embed Pods Frameworks */,
AF570E8AAEEA12D52BD19B4E /* [CP] Copy Pods Resources */,
);
buildRules = (
@@ -288,23 +290,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 590CF992B35CC61AF9AA4341 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -396,6 +381,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 8420D0082F3D9F7E006DB6CC /* NativeFirstPage.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
@@ -526,8 +512,6 @@
"-framework",
"\"package_info_plus\"",
"-framework",
- "\"path_provider_foundation\"",
- "-framework",
"\"permission_handler_apple\"",
"-framework",
"\"shared_preferences_foundation\"",
@@ -565,8 +549,6 @@
"-framework",
"\"package_info_plus\"",
"-framework",
- "\"path_provider_foundation\"",
- "-framework",
"\"permission_handler_apple\"",
"-framework",
"\"shared_preferences_foundation\"",
@@ -794,8 +776,6 @@
"-framework",
"\"package_info_plus\"",
"-framework",
- "\"path_provider_foundation\"",
- "-framework",
"\"permission_handler_apple\"",
"-framework",
"\"shared_preferences_foundation\"",
@@ -833,8 +813,6 @@
"-framework",
"\"package_info_plus\"",
"-framework",
- "\"path_provider_foundation\"",
- "-framework",
"\"permission_handler_apple\"",
"-framework",
"\"shared_preferences_foundation\"",
@@ -899,8 +877,6 @@
"-framework",
"\"package_info_plus\"",
"-framework",
- "\"path_provider_foundation\"",
- "-framework",
"\"permission_handler_apple\"",
"-framework",
"\"shared_preferences_foundation\"",
diff --git a/ln_jq_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ln_jq_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index e3773d4..b213f83 100644
--- a/ln_jq_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/ln_jq_app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -61,6 +61,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
+ enableGPUFrameCaptureMode = "3"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
Bool {
- GeneratedPluginRegistrant.register(with: self)
+
+ GeneratedPluginRegistrant.register(with: self)
+
+ AMapNavSDKManager.shared().config(withKey: kAMapKey)
+
+ // 注册平台视图工厂
+ let registrar = self.registrar(forPlugin: "NativeFirstPagePlugin")
+
+ let controller = window?.rootViewController as! FlutterViewController
+ let nativeViewFactory = NativeViewFactory(messenger: controller.binaryMessenger)
+
+ registrar?.register(
+ nativeViewFactory,
+ withId: "NativeFirstPage"
+ )
+
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+
}
+
+
+
}
+
+
+
+
+// 创建视图工厂
+class NativeViewFactory: NSObject, FlutterPlatformViewFactory {
+ private var messenger: FlutterBinaryMessenger
+
+ init(messenger: FlutterBinaryMessenger) {
+ self.messenger = messenger
+ super.init()
+ }
+
+ func create(
+ withFrame frame: CGRect,
+ viewIdentifier viewId: Int64,
+ arguments args: Any?
+ ) -> FlutterPlatformView {
+ return NativeFlutterView(frame: frame, viewId: viewId, args: args)
+ }
+
+ func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
+ return FlutterStandardMessageCodec.sharedInstance()
+ }
+}
+
+class NativeFlutterView: NSObject, FlutterPlatformView {
+ private var _view: UIView
+ private var _nativeVC: UIViewController
+
+ init(frame: CGRect, viewId: Int64, args: Any?) {
+ // 创建原生的 ViewController 视图
+ let nativeVC = AMapNavSDKManager.shared().targetVC;
+// let nativeVC = NativeFirstPage();
+
+ self._nativeVC = nativeVC
+
+ print("---frame: \(frame)");
+
+ _view = nativeVC.view
+ _view.isUserInteractionEnabled = true
+ _view.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame))
+ super.init()
+ }
+
+ func view() -> UIView {
+ return _view
+ }
+}
+
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
index d57f16b..72f31e6 100644
--- a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -13,7 +13,7 @@
"size" : "20x20"
},
{
- "filename" : "Icon-App-29x29@1x.png",
+ "filename" : "Icon-App-29x29 1.png",
"idiom" : "iphone",
"scale" : "1x",
"size" : "29x29"
@@ -31,25 +31,25 @@
"size" : "29x29"
},
{
- "filename" : "Icon-App-80x80.jpg",
+ "filename" : "Icon-App-80x80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
- "filename" : "Icon-App-120x120.jpg",
+ "filename" : "Icon-App-120x120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
- "filename" : "Icon-App-120x120 1.jpg",
+ "filename" : "Icon-App-120x120 1.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
- "filename" : "Icon-App-180.jpg",
+ "filename" : "Icon-App-180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
@@ -61,19 +61,19 @@
"size" : "20x20"
},
{
- "filename" : "Icon-App-20x20@2x.png",
+ "filename" : "Icon-App-20x20@2x 1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
- "filename" : "Icon-App-29x29@1x.png",
+ "filename" : "Icon-App-29x29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
- "filename" : "Icon-App-29x29@2x.png",
+ "filename" : "Icon-App-29x29@2x 1.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
@@ -91,7 +91,7 @@
"size" : "40x40"
},
{
- "filename" : "Icon-App-76x76@1x.png",
+ "filename" : "Icon-App-76x76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index d1319d7..eaaf18f 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120 1.jpg b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120 1.jpg
deleted file mode 100644
index 4b43452..0000000
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120 1.jpg and /dev/null differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120 1.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120 1.png
new file mode 100644
index 0000000..c921249
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120 1.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120.jpg b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120.jpg
deleted file mode 100644
index 4b43452..0000000
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120.jpg and /dev/null differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120.png
new file mode 100644
index 0000000..c921249
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-120x120.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-180.jpg b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-180.jpg
deleted file mode 100644
index 0e5324b..0000000
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-180.jpg and /dev/null differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-180.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-180.png
new file mode 100644
index 0000000..1231afa
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-180.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
index 504e957..f57ed52 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x 1.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x 1.png
new file mode 100644
index 0000000..a06e413
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x 1.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
index 494a3cb..a06e413 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
index a1c7d8d..df533cb 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29 1.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29 1.png
new file mode 100644
index 0000000..ef8019b
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29 1.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29.png
new file mode 100644
index 0000000..ef8019b
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
deleted file mode 100644
index d892c49..0000000
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png
new file mode 100644
index 0000000..3d206b9
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
index a32483e..3d206b9 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
index 8e3fb87..b252bc1 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76.png
new file mode 100644
index 0000000..27618d9
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
deleted file mode 100644
index 36ef160..0000000
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
index 8f7c86a..b3f0ff1 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-80x80.jpg b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-80x80.jpg
deleted file mode 100644
index a8feb00..0000000
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-80x80.jpg and /dev/null differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-80x80.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-80x80.png
new file mode 100644
index 0000000..c474e2a
Binary files /dev/null and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-80x80.png differ
diff --git a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
index a00bd83..e4e54ba 100644
Binary files a/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ln_jq_app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/ln_jq_app/ios/Runner/Info.plist b/ln_jq_app/ios/Runner/Info.plist
index 629759d..392eb31 100644
--- a/ln_jq_app/ios/Runner/Info.plist
+++ b/ln_jq_app/ios/Runner/Info.plist
@@ -2,8 +2,6 @@
-
-
CADisableMinimumFrameDurationOnPhone
CFBundleDevelopmentRegion
@@ -16,6 +14,11 @@
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
+ CFBundleLocalizations
+
+ zh-Hans
+ en
+
CFBundleName
ln_jq_app
CFBundlePackageType
@@ -28,6 +31,10 @@
$(FLUTTER_BUILD_NUMBER)
ITSAppUsesNonExemptEncryption
+ LSApplicationQueriesSchemes
+
+ iosamap
+
LSRequiresIPhoneOS
NSCameraUsageDescription
@@ -44,6 +51,12 @@
需要访问您的相册以选择二维码图片进行识别
UIApplicationSupportsIndirectInputEvents
+ UIBackgroundModes
+
+ remote-notification
+ fetch
+ location
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -64,6 +77,7 @@
uses
+
UIBackgroundModes
remote-notification
@@ -82,5 +96,6 @@
LSSupportsOpeningDocumentsInPlace
+
diff --git a/ln_jq_app/ios/Runner/NativeFirstPage.swift b/ln_jq_app/ios/Runner/NativeFirstPage.swift
new file mode 100644
index 0000000..04ee6cb
--- /dev/null
+++ b/ln_jq_app/ios/Runner/NativeFirstPage.swift
@@ -0,0 +1,66 @@
+//
+// NativeFirstPage.swift
+// Runner
+//
+// Created by admin on 2026/2/9.
+//
+
+import UIKit
+
+class NativeFirstPage: UIViewController {
+var lable:UILabel!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Do any additional setup after loading the view.
+ view.backgroundColor = .white
+
+ // 创建原生UI
+ let label = UILabel()
+ label.text = "iOS 原生页面."
+ label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
+ label.textAlignment = .center
+ label.translatesAutoresizingMaskIntoConstraints = false
+
+ let button = UIButton(type: .custom)
+ button.setTitle("点击原生按钮", for: .normal)
+ button.titleLabel?.font = UIFont.systemFont(ofSize: 18)
+ button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
+ button.translatesAutoresizingMaskIntoConstraints = false
+ button.backgroundColor = .blue
+
+ view.addSubview(label)
+ view.addSubview(button)
+
+ NSLayoutConstraint.activate([
+ label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+ label.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -50),
+
+ button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 30),
+ button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
+ ])
+
+ self.lable = label
+
+ }
+
+ @objc func buttonTapped() {
+ self.lable.text = "click...";
+
+ // 原生按钮点击事件
+ let alert = UIAlertController(
+ title: "原生弹窗",
+ message: "来自 iOS 原生的提示",
+ preferredStyle: .alert
+ )
+ alert.addAction(UIAlertAction(title: "确定", style: .default))
+ present(alert, animated: true)
+ }
+
+
+ override func touchesBegan(_ touches: Set, with event: UIEvent?) {
+ view.backgroundColor = .orange
+ }
+
+}
diff --git a/ln_jq_app/ios/Runner/Runner-Bridging-Header.h b/ln_jq_app/ios/Runner/Runner-Bridging-Header.h
index 308a2a5..758fa21 100644
--- a/ln_jq_app/ios/Runner/Runner-Bridging-Header.h
+++ b/ln_jq_app/ios/Runner/Runner-Bridging-Header.h
@@ -1 +1,2 @@
#import "GeneratedPluginRegistrant.h"
+#import
diff --git a/ln_jq_app/ios/Runner/Runner.entitlements b/ln_jq_app/ios/Runner/Runner.entitlements
index 903def2..090fb70 100644
--- a/ln_jq_app/ios/Runner/Runner.entitlements
+++ b/ln_jq_app/ios/Runner/Runner.entitlements
@@ -4,5 +4,9 @@
aps-environment
development
+ com.apple.developer.background-tasks.continued-processing.gpu
+
+ com.apple.developer.location.push
+
diff --git a/ln_jq_app/lib/main.dart b/ln_jq_app/lib/main.dart
index b08945a..714738e 100644
--- a/ln_jq_app/lib/main.dart
+++ b/ln_jq_app/lib/main.dart
@@ -71,6 +71,9 @@ void initHttpSet() {
HttpService.to.dio.interceptors.add(TokenInterceptor(tokenKey: 'asoco-token'));
HttpService.to.setOnResponseHandler((response) async {
try {
+ if (response.data == null) {
+ return null;
+ }
final baseModel = BaseModel.fromJson(response.data);
if (baseModel.code == 0 || baseModel.code == 200) {
return null;
diff --git a/ln_jq_app/lib/pages/c_page/base_widgets/NativePageIOS.dart b/ln_jq_app/lib/pages/c_page/base_widgets/NativePageIOS.dart
new file mode 100644
index 0000000..55c15b4
--- /dev/null
+++ b/ln_jq_app/lib/pages/c_page/base_widgets/NativePageIOS.dart
@@ -0,0 +1,129 @@
+import 'dart:io';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
+import 'package:getx_scaffold/common/common.dart';
+import 'package:getx_scaffold/getx_scaffold.dart';
+
+/// 原生地图页面
+class NativePageIOS extends StatefulWidget {
+ const NativePageIOS({super.key});
+
+ @override
+ State createState() => _NativePageIOSState();
+}
+
+class _NativePageIOSState extends State {
+ bool _androidPermissionReady = false;
+
+ @override
+ void initState() {
+ super.initState();
+ if (Platform.isAndroid) {
+ requestAndroidPermission();
+ } else {
+ // iOS 已经在原生端处理好,直接置为 true
+ _androidPermissionReady = true;
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (Platform.isIOS) {
+ return _buildIOSView(context);
+ } else if (Platform.isAndroid) {
+ // 如果安卓权限还没处理完,显示加载中,不加载原生 View
+ if (!_androidPermissionReady) {
+ return const Center(
+ child: CircularProgressIndicator(color: Color(0xFF017137)),
+ );
+ }
+ return _buildAndroidView(context);
+ } else {
+ return const Center(child: Text('不支持的平台', style: TextStyle(fontSize: 16)));
+ }
+ }
+
+ /// 构建iOS Platform View
+ Widget _buildIOSView(BuildContext context) {
+ return Container(
+ width: MediaQuery.of(context).size.width,
+ height: MediaQuery.of(context).size.height - 100,
+ color: Colors.white,
+ child: UiKitView(
+ viewType: 'NativeFirstPage',
+ // 与iOS原生端注册的标识一致
+ gestureRecognizers: >{}.toSet(),
+ hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+ creationParamsCodec: const StandardMessageCodec(),
+ layoutDirection: TextDirection.ltr,
+ ),
+ );
+ }
+
+ /// 构建Android Platform View
+ Widget _buildAndroidView(BuildContext context) {
+ return Container(
+ width: MediaQuery.of(context).size.width,
+ height: MediaQuery.of(context).size.height - 100,
+ color: Colors.white,
+ child: PlatformViewLink(
+ viewType: 'NativeFirstPage',
+ surfaceFactory: (context, controller) {
+ return AndroidViewSurface(
+ controller: controller as AndroidViewController,
+ gestureRecognizers: const >{},
+ hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+ );
+ },
+ onCreatePlatformView: (params) {
+ // 使用 initSurfaceAndroidView 强制开启 Hybrid Composition
+ return PlatformViewsService.initSurfaceAndroidView(
+ id: params.id,
+ viewType: 'NativeFirstPage',
+ layoutDirection: TextDirection.ltr,
+ creationParams: {},
+ // 你的参数
+ creationParamsCodec: const StandardMessageCodec(),
+ onFocus: () {
+ params.onFocusChanged(true);
+ },
+ )
+ ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
+ ..create();
+ },
+ ),
+ );
+ }
+
+ void requestAndroidPermission() async {
+ try {
+ final deviceInfo = await DeviceInfoPlugin().androidInfo;
+ final sdkInt = deviceInfo.version.sdkInt;
+
+ List permissions = [Permission.location];
+
+ if (sdkInt < 33) {
+ permissions.add(Permission.storage);
+ }
+
+ // 发起请求
+ Map statuses = await permissions.request();
+
+ if (statuses[Permission.location]?.isDenied ?? false) {
+ Toast.show("定位权限被拒绝,会影响相关功能使用");
+ }
+ } catch (e) {
+ debugPrint("权限申请异常: $e");
+ } finally {
+ if (mounted) {
+ setState(() {
+ _androidPermissionReady = true;
+ });
+ }
+ }
+ }
+}
diff --git a/ln_jq_app/lib/pages/c_page/base_widgets/view.dart b/ln_jq_app/lib/pages/c_page/base_widgets/view.dart
index 314ba7b..6724c3b 100644
--- a/ln_jq_app/lib/pages/c_page/base_widgets/view.dart
+++ b/ln_jq_app/lib/pages/c_page/base_widgets/view.dart
@@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
-import 'package:get/get.dart';
import 'package:getx_scaffold/getx_scaffold.dart';
import 'package:ln_jq_app/common/login_util.dart';
+import 'package:ln_jq_app/pages/c_page/base_widgets/NativePageIOS.dart';
import 'package:ln_jq_app/pages/c_page/car_info/view.dart';
-import 'package:ln_jq_app/pages/c_page/mall/mall_view.dart';
-import 'package:ln_jq_app/pages/c_page/map/view.dart';
+
import 'package:ln_jq_app/pages/c_page/mine/view.dart';
import 'package:ln_jq_app/pages/c_page/reservation/view.dart';
@@ -34,7 +33,7 @@ class BaseWidgetsPage extends GetView {
}
List _buildPages() {
- return [ReservationPage(), MapPage(), MallPage(), CarInfoPage(), MinePage()];
+ return [ReservationPage(), NativePageIOS(), CarInfoPage(), MinePage()];
}
// 自定义导航栏 (悬浮胶囊样式)
diff --git a/ln_jq_app/lib/storage_service.dart b/ln_jq_app/lib/storage_service.dart
index 6549205..99a2b84 100644
--- a/ln_jq_app/lib/storage_service.dart
+++ b/ln_jq_app/lib/storage_service.dart
@@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:getx_scaffold/common/utils/log_util.dart';
import 'package:ln_jq_app/common/model/vehicle_info.dart';
+import 'package:shared_preferences/shared_preferences.dart';
/// 定义登录渠道的枚举类型
enum LoginChannel {
@@ -105,10 +106,16 @@ class StorageService extends GetxService {
await _box.write(_nameKey, name);
await _box.write(_phoneKey, phone);
await _box.write(_idCardKey, idCard);
+
+ final SharedPreferences prefs = await SharedPreferences.getInstance();
+ await prefs.setString('token', token);
}
Future saveVehicleInfo(VehicleInfo data) async {
await _box.write(_vehicleInfoKey, vehicleInfoToJson(data));
+
+ final SharedPreferences prefs = await SharedPreferences.getInstance();
+ await prefs.setString('plateNumber', data.plateNumber);
}
Future saveStationCredentials(String account, String password) async {
@@ -128,6 +135,10 @@ class StorageService extends GetxService {
Future clearVehicleInfo() async {
await _box.remove(_vehicleInfoKey);
+
+ final SharedPreferences prefs = await SharedPreferences.getInstance();
+ await prefs.remove('plateNumber');
+ await prefs.remove('token');
}
Future clearStationCredentials() async {
diff --git a/ln_jq_app/pubspec.lock b/ln_jq_app/pubspec.lock
index 558172d..18de9b6 100644
--- a/ln_jq_app/pubspec.lock
+++ b/ln_jq_app/pubspec.lock
@@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: archive
- sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
+ sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
url: "https://pub.flutter-io.cn"
source: hosted
- version: "4.0.7"
+ version: "4.0.9"
args:
dependency: transitive
description:
@@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: async
- sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.13.0"
+ version: "2.13.1"
badges:
dependency: transitive
description:
@@ -117,10 +117,10 @@ packages:
dependency: transitive
description:
name: cross_file
- sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608"
+ sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "0.3.5+1"
+ version: "0.3.5+2"
crypto:
dependency: transitive
description:
@@ -141,18 +141,18 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
- sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.0.8"
+ version: "1.0.9"
dbus:
dependency: transitive
description:
name: dbus
- sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
+ sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270
url: "https://pub.flutter-io.cn"
source: hosted
- version: "0.7.11"
+ version: "0.7.12"
decimal:
dependency: transitive
description:
@@ -181,18 +181,18 @@ packages:
dependency: transitive
description:
name: dio
- sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
+ sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c
url: "https://pub.flutter-io.cn"
source: hosted
- version: "5.9.0"
+ version: "5.9.2"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
- sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
+ sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
dropdown_button2:
dependency: "direct main"
description:
@@ -245,10 +245,10 @@ packages:
dependency: transitive
description:
name: ffi
- sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
+ sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.1.4"
+ version: "2.2.0"
file:
dependency: transitive
description:
@@ -338,10 +338,10 @@ packages:
dependency: transitive
description:
name: flutter_inappwebview_internal_annotations
- sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd"
+ sha256: e30fba942e3debea7b7e6cdd4f0f59ce89dd403a9865193e3221293b6d1544c6
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.2.0"
+ version: "1.3.0"
flutter_inappwebview_ios:
dependency: transitive
description:
@@ -415,10 +415,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
- sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
+ sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.0.33"
+ version: "2.0.34"
flutter_screenutil:
dependency: transitive
description:
@@ -439,10 +439,10 @@ packages:
dependency: transitive
description:
name: flutter_svg
- sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95"
+ sha256: "1ded017b39c8e15c8948ea855070a5ff8ff8b3d5e83f3446e02d6bb12add7ad9"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.2.3"
+ version: "2.2.4"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -585,10 +585,10 @@ packages:
dependency: "direct main"
description:
name: image
- sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c"
+ sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
url: "https://pub.flutter-io.cn"
source: hosted
- version: "4.7.2"
+ version: "4.8.0"
image_picker:
dependency: "direct main"
description:
@@ -601,10 +601,10 @@ packages:
dependency: transitive
description:
name: image_picker_android
- sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677"
+ sha256: "9eae0cbd672549dacc18df855c2a23782afe4854ada5190b7d63b30ee0b0d3fd"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "0.8.13+10"
+ version: "0.8.13+15"
image_picker_for_web:
dependency: transitive
description:
@@ -745,10 +745,10 @@ packages:
dependency: "direct main"
description:
name: mobile_scanner
- sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044
+ sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce
url: "https://pub.flutter-io.cn"
source: hosted
- version: "7.1.4"
+ version: "7.2.0"
modal_bottom_sheet:
dependency: transitive
description:
@@ -809,10 +809,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
- sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
+ sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.2.22"
+ version: "2.2.23"
path_provider_foundation:
dependency: transitive
description:
@@ -897,10 +897,10 @@ packages:
dependency: transitive
description:
name: petitparser
- sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
+ sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "7.0.1"
+ version: "7.0.2"
photo_view:
dependency: "direct main"
description:
@@ -937,10 +937,10 @@ packages:
dependency: transitive
description:
name: posix
- sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
+ sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "6.0.3"
+ version: "6.5.0"
pull_to_refresh:
dependency: "direct main"
description:
@@ -966,21 +966,21 @@ packages:
source: hosted
version: "4.1.0"
shared_preferences:
- dependency: transitive
+ dependency: "direct main"
description:
name: shared_preferences
- sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
+ sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.5.4"
+ version: "2.5.5"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
- sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
+ sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.4.18"
+ version: "2.4.23"
shared_preferences_foundation:
dependency: transitive
description:
@@ -1001,10 +1001,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_platform_interface
- sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+ sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "2.4.1"
+ version: "2.4.2"
shared_preferences_web:
dependency: transitive
description:
@@ -1038,10 +1038,10 @@ packages:
dependency: transitive
description:
name: source_span
- sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.10.1"
+ version: "1.10.2"
stack_trace:
dependency: transitive
description:
@@ -1110,10 +1110,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
- sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611"
+ sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "6.3.28"
+ version: "6.3.29"
url_launcher_ios:
dependency: transitive
description:
@@ -1166,18 +1166,18 @@ packages:
dependency: transitive
description:
name: uuid
- sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
+ sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "4.5.2"
+ version: "4.5.3"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
- sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
+ sha256: "7076216a10d5c390315fbe536a30f1254c341e7543e6c4c8a815e591307772b1"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.19"
+ version: "1.1.20"
vector_graphics_codec:
dependency: transitive
description:
@@ -1190,10 +1190,10 @@ packages:
dependency: transitive
description:
name: vector_graphics_compiler
- sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
+ sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74"
url: "https://pub.flutter-io.cn"
source: hosted
- version: "1.1.19"
+ version: "1.2.0"
vector_math:
dependency: transitive
description:
diff --git a/ln_jq_app/pubspec.yaml b/ln_jq_app/pubspec.yaml
index 3c56192..2b024bd 100644
--- a/ln_jq_app/pubspec.yaml
+++ b/ln_jq_app/pubspec.yaml
@@ -40,6 +40,7 @@ dependencies:
encrypt: ^5.0.3
get_storage: ^2.1.1
+ shared_preferences: ^2.5.4
flutter_native_splash: ^2.4.7
dropdown_button2: ^2.3.8