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